Backend Development 13 min read

Three‑Step Strategy for Identifying and Removing Zombie Services, Methods, and Component Dependencies

This article presents a detailed three‑step plan used by Zhezhuan to detect and eliminate zombie services, unused code methods, and obsolete component dependencies through monitoring, static analysis with Spoon, and Java‑agent based runtime tracing, achieving significant resource savings and improved code health.

Zhuanzhuan Tech
Zhuanzhuan Tech
Zhuanzhuan Tech
Three‑Step Strategy for Identifying and Removing Zombie Services, Methods, and Component Dependencies

Background: In 2023 Zhezhuan celebrated its eighth anniversary, and many legacy services, code, and component dependencies have become obsolete, consuming resources without providing business value.

Step 1 – Discover and decommission zombie services: A zombie service is defined as a service with zero business traffic that still occupies server resources. Traffic entry types include nginx‑forwarded HTTP/WebSocket, RPC, MQ consumption, scheduled tasks, and private‑protocol jobs. For the first four types Prometheus metrics are used; the fifth requires custom RD metrics. A daily job extracts services with no traffic for a month and notifies owners. The de‑registration workflow removes the service and its code, with a 15‑day rollback window in case of issues.

Step 2 – Discover and remove zombie methods: Zombie methods are those never invoked at runtime. The full method list is obtained by scanning source code with the Spoon tool. Example code: private static void doScanJavaFile(String javaVersion, File javaFile, List<SourceCodeJavaMethod> sourceCodeJavaMethodList) { Launcher launcher = new Launcher(); launcher.addInputResource(new FileSystemFile(javaFile)); launcher.getEnvironment().setNoClasspath(true); launcher.getEnvironment().setAutoImports(true); launcher.getEnvironment().setComplianceLevel(Integer.parseInt((javaVersion.contains(".") ? javaVersion.substring(2) : javaVersion))); Collection<CtType<?>> allTypes = launcher.buildModel().getAllTypes(); for (CtType<?> type : allTypes) { String className = type.getQualifiedName(); for (CtMethod<?> method : type.getMethods()) { SourcePosition position = method.getPosition(); sourceCodeJavaMethodList.add(new SourceCodeJavaMethod(className, method.getSignature(), position.getEndLine() - position.getLine() + 1)); } } } Active methods are collected at runtime via three approaches: Spring AOP (high overhead and invasive), Java agent bytecode enhancement (non‑invasive but complex), and ServiceAbility (SA) Agent (low overhead, non‑invasive). A comparison table shows SA as the preferred solution despite a temporary "stop‑the‑world" pause during data collection. Detailed SA usage code is provided: public class KlassVisitor implements SystemDictionary.ClassVisitor { private List<CalledMethod> out; public KlassVisitor(List<CalledMethod> out) { this.out = out; } @Override public void visit(Klass klass) { if (klass instanceof InstanceKlass) { String className = klass.getName().asString(); MethodArray methods = ((InstanceKlass) klass).getMethods(); for (int i = 0; i < methods.length(); i++) { Method method = methods.at(i); if (method.isNative()) return; long invocationCount = method.getInvocationCount() >> 3; if (invocationCount > 0) { out.add(new CalledMethod(className, method.getName().asString(), method.getSignature().asString(), invocationCount)); } } } } } public class CodeBlobVisitor implements CodeCacheVisitor { private List<CalledMethod> out; public CodeBlobVisitor(List<CalledMethod> out) { this.out = out; } @Override public void visit(CodeBlob codeBlob) { if (codeBlob == null) return; NMethod nMethodOrNull = codeBlob.asNMethodOrNull(); if (nMethodOrNull == null) return; Method method = nMethodOrNull.getMethod(); if (method == null || method.isNative()) return; String className = method.getMethodHolder().getName().asString(); String methodName = method.getName().asString(); String signature = method.getSignature().asString(); long invocationCount = method.getInvocationCount() >> 3; out.add(new CalledMethod(className, methodName, signature, invocationCount)); } } Data collection is scheduled 30 seconds after traffic is shut down, then the JVM is restarted to restore traffic. For services with special traffic patterns, manual commands or Apollo configuration can be used. Step 3 – Discover and drop zombie component dependencies: Most component usage is already monitored via Prometheus. For open‑source middleware lacking built‑in metrics, a Java‑agent is used to instrument libraries (e.g., MySQL driver) and emit usage data. Summary and results: From October 1 to December 20, 30 services (68 instances) were decommissioned, saving 246 GB of memory. Code‑utilization metrics show only 43 % of methods and 50 % of lines are actively used. The component‑dependency cleanup feature is newly launched and data is still limited.

Javamonitoringbackend optimizationservice governancezombie code
Zhuanzhuan Tech
Written by

Zhuanzhuan Tech

A platform for Zhuanzhuan R&D and industry peers to learn and exchange technology, regularly sharing frontline experience and cutting‑edge topics. We welcome practical discussions and sharing; contact waterystone with any questions.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.