This article demonstrates how to use Arthas, a popular diagnostic tool for Java appplications, to troubleshoot common production issues. We'll walk through a sample project with three problematic endpoints and then show how Arthas commands help identify and fix them.
Setting Up a Demo Application
First, create a Spring Boot application with three REST endpoints, each simulating a different issue:
/cpu-spike– causes high CPU usage (~100%)/deadlock– creates a thread deadlock/typo– returns a string with a spelling error
Controller class (DiagnosticController.java):
@RestController
public class DiagnosticController {
@Autowired
private DiagnosticService service;
@GetMapping("/cpu-spike")
public String triggerCpuSpike() {
service.simulateHighCPU();
return "cpu-spike-triggered";
}
@GetMapping("/deadlock")
public String triggerDeadlock() {
service.createDeadlock();
return "deadlock-triggered";
}
@GetMapping("/typo")
public String showTypo() {
return service.getOutput();
}
}
Service class (DiagnosticService.java):
@Service
public class DiagnosticService {
public void simulateHighCPU() {
new Thread(() -> { while (true) {} }).start();
}
public void createDeadlock() {
Object lockA = new Object();
Object lockB = new Object();
Thread t1 = new Thread(() -> {
synchronized (lockA) {
System.out.println(Thread.currentThread() + " acquired lockA");
try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
System.out.println(Thread.currentThread() + " waiting for lockB");
synchronized (lockB) {
System.out.println(Thread.currentThread() + " acquired lockB");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lockB) {
System.out.println(Thread.currentThread() + " acquired lockB");
try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
System.out.println(Thread.currentThread() + " waiting for lockA");
synchronized (lockA) {
System.out.println(Thread.currentThread() + " acquired lockA");
}
}
});
t1.start();
t2.start();
}
public String getOutput() {
return "Helloo Arthas!"; // deliberate typo
}
}
Package the application as a JAR and run it on a server:
nohup java -jar diagnostics-demo.jar > output.log &
Access the endpoints via browser or curl. Use top to see high CPU usage caused by the first endpoint.
Downloading and Starting Arthas
Arthas can be downloaded from its official site. On the server, run:
curl -O https://arthas.aliyun.com/arthas-boot.jar
Then start Arthas with:
java -jar arthas-boot.jar
Arthas will list all running Java processes. Select the one corresponding to your demo application (e.g., by typing the index 1). This opens the Arthas command console.
Common Arthas Commands
dashboard
Shows a real-time panel of system metrics (CPU, memory, threads). Updates every 5 seconds by default.
-i: interval in ms (default 5000)-n: number of refreshes
thread
Displays thread information and stack traces.
id: a specific thread ID (from dashboard)-b: identifies threads that are blocking others
Diagnosing Issue 1 – High CPU
Run dashboard in Arthas. Access the /cpu-spike endpoint. Observe the panel – one thread shows extremely high CPU usage.
Press Ctrl+C to exit dashboard, then run thread <thread-id> to see its stack trace. The output points to line 13 of DiagnosticService.java (the infinite loop). This allows you to locate and fix the code in your IDE.
Diagnosing Issue 2 – Deadlock
Start dashboard again, then access the /deadlock endpoint. The panel will show threads in BLOCKED state.
Use thread -b to find the thread causing the blockage. The output reveals the exact lines where the deadlock occurs (the nested synchronized blocks).
watch
Observes method invocations – parameters, return values, exceptions, etc.
Example: watch the simulateHighCPU method with depth 2:
watch com.example.diagnostics.service.DiagnosticService simulateHighCPU -x 2
Then access the endpoint. Arthas prints the method call details including class, invocation count, execution time, and return value.
To monitor exceptions, modify the service to intentionally throw an error in getOutput():
public String getOutput() {
System.out.println(1/0); // artificial exception
return "Helloo Arthas!";
}
Re-package and deploy, then run:
watch com.example.diagnostics.service.DiagnosticService getOutput '{throwExp}' -e -x 2
Access the endpoint – Arthas displays the exception details. This is useful for debugging production issues.
trace
Prints the internal call path of a method and the time spent at each node.
Example: trace the /cpu-spike endpoint:
trace com.example.diagnostics.controller.DiagnosticController triggerCpuSpike
Access the endpoint – Arthas shows the invocation tree and durations. Use #cost to filter by execution time, e.g., show only nodes taking more than 0.1ms.
jad
Decompiles a loaded class to Java source code.
Example: decompile the service class and save to a file:
jad com.example.diagnostics.service.DiagnosticService > /home/DiagnosticService.java
The output includes class loader info and line numbers. To get clean source code without line numbers, use:
jad --source-only --lineNumber false com.example.diagnostics.service.DiagnosticService > /home/DiagnosticService.java
sc
Search for loaded classes in the JVM. Use -d to show detailed information.
sc -d com.example.diagnostics.service.DiagnosticService
The output includes the class loader hash, which is needed later for compilation.
mc
Compiles a .java file into a .class file.
-c: class loader hash (obtained fromsc -d)-d: output directory
Example:
mc -c 1593948d /home/DiagnosticService.java -d /home
redefine
Loads an external .class file to replace an existing class in the running JVM.
redefine /home/com/example/diagnostics/service/DiagnosticService.class
Optionally specify the class loader with -c.
Diagnosing Issue 3 – Hot-Fixing the Typo (Live Update)
Use jad, mc, and redefine together to fix the spelling error without restarting the application.
- Decompile the service class:
jad --source-only --lineNumber false com.example.diagnostics.service.DiagnosticService > /home/DiagnosticService.java - Edit the file with
vimand change"Helloo Arthas!"to"Hello Arthas!". - Get the class loader hash:
sc -d com.example.diagnostics.service.DiagnosticService(note the hash value). - Compile the edited Java file:
mc -c <hash> /home/DiagnosticService.java -d /home - Replace the class:
redefine /home/com/example/diagnostics/service/DiagnosticService.class
Now access /typo – the response will show "Hello Arthas!" instead of the old misspelled text.
Note: redefine has limitations; not all code changes are effective (e.g., adding/removing methods or fields). See the official documentation for details.
Other Auxiliary Commands
cat– view file contentscls– clear the screenstop– shut down the Arthas serverexit– exit the current Arthas client (others remain unaffected)history– list command history
Further Reading
For a complete list of commands and detailed usage, refer to the official Arthas documentation.