Using Arthas for Java Debugging and Common Commands

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 from sc -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.

  1. Decompile the service class: jad --source-only --lineNumber false com.example.diagnostics.service.DiagnosticService > /home/DiagnosticService.java
  2. Edit the file with vim and change "Helloo Arthas!" to "Hello Arthas!".
  3. Get the class loader hash: sc -d com.example.diagnostics.service.DiagnosticService (note the hash value).
  4. Compile the edited Java file: mc -c <hash> /home/DiagnosticService.java -d /home
  5. 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 contents
  • cls – clear the screen
  • stop – shut down the Arthas server
  • exit – 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.

Tags: Arthas java JVM debugging performance monitoring

Posted on Wed, 15 Jul 2026 16:13:33 +0000 by marco839