启用分层编译时如何使用GDB调试Java进程?
When tiered compilation is active, the JVM compiles methods incrementally—starting with interpretation, then moving to C1 (client compiler) optimizations, and finally C2 (server compiler) optimizations. Each compilation stage produces code at a new memory address, and the JVM may replace existing compiled code with newer versions, which is why your static GDB breakpoints stop working. Here are practical solutions to debug effectively:
1. Track Compilation Events and Addresses in Real-Time
Enable JVM diagnostic flags to log every compilation event and corresponding memory addresses. This lets you update your GDB breakpoints as the JVM compiles (and recompiles) your target method.
Use this startup command:
java -XX:+TieredCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+PrintCompilation -XX:+DebugNonSafepoints Main
Let’s break down the key flags:
-XX:+PrintCompilation: Prints a log entry every time a method is compiled, including the tier level (e.g.,tier1for C1,tier4for C2) and method name.-XX:+PrintAssembly: Disassembles compiled code and outputs the full memory range of each compiled method version.-XX:+DebugNonSafepoints: Ensures every instruction has a safepoint, making GDB breakpoints more reliable (without this, some instructions might not trigger breaks).
When you see output like:
Compiled method (c2) 55 1 Main::m (1 bytes) total in heap [0x00007f25d506fc10,0x00007f25d506fdc8]
You can immediately switch to your GDB session and set a breakpoint on the new address (e.g., 0x00007f25d506fd40 for the main code entry point).
2. Disable Code Replacement to Stabilize Addresses
If you want to prevent the JVM from replacing compiled code (at the cost of some performance), use these flags to lock in compiled method addresses:
java -XX:+TieredCompilation -XX:+UnlockDiagnosticVMOptions -XX:-UseCodeCacheFlushing -XX:-UseOnStackReplacement Main
-XX:-UseCodeCacheFlushing: Prevents the JVM from evicting old compiled code from the code cache.-XX:-UseOnStackReplacement: Disables on-stack replacement (OSR), which stops the JVM from replacing a method’s code while it’s executing on the stack.
With these flags, each method’s compiled version stays at its initial address, so your GDB breakpoints will work consistently.
3. Use JVM TI Tools (Like JDB) for Method-Level Breakpoints
Instead of dealing with low-level memory addresses, use Java Debugger (JDB) which integrates with the JVM Tool Interface (JVM TI). JDB automatically handles code replacement by updating breakpoints whenever the JVM recompiles a method.
To use JDB:
- Attach to your running Java process:
jdb -attach <your-java-process-pid> - Set a breakpoint directly on your target method:
stop in Main.m
The JVM will pause execution whenever Main.m is called, regardless of which compilation tier is active.
4. Dynamically Set Breakpoints via GDB Compilation Callbacks
For more advanced debugging, you can set a GDB breakpoint on the JVM’s internal compilation functions to automatically break when your target method is compiled.
In GDB:
# Break when the JVM starts compiling a method break CompileBroker::compile_method commands # Print the name of the method being compiled printf "Compiling method: %s\n", (char*)((methodOop)$rdi)->name()->as_C_string() # If it's your target method, continue until compilation finishes and set a breakpoint if strcmp((char*)((methodOop)$rdi)->name()->as_C_string(), "m") == 0 continue # After compilation, inspect JVM internals to get the method's code address and set a breakpoint end end
This approach requires some familiarity with the JVM’s internal structures, but it lets you automate breakpoint setup as methods are compiled.
内容的提问来源于stack exchange,提问作者St.Antario

