Java Profiling
CodeXray profiles Java applications via the Node Agent using a three-part approach: JVM attach, perfmap dumping, and eBPF stack sampling. The end result is a CPU FlameGraph in the Profiling tab with your Java method names properly resolved — at least, once one JVM flag is set.
Add -XX:+PreserveFramePointer to your JVM options. Without it, your JIT-compiled Java methods will appear in the FlameGraph as unresolved hex addresses. With it, they resolve to com.yourcompany.Order.process style names.
How It Works
When the Node Agent sees a Java process on the host:
- It attaches to the JVM via the attach socket (
/tmp/.java_pid<N>or the equivalent inside the pod's namespace) using the Hotspot attach protocol. - It asks the JVM to dump a perfmap — a file the JVM writes describing which JIT-compiled method lives at which memory address.
- The eBPF profiler samples stack traces the normal way. When it hits an address inside the JIT-compiled code region, it uses the perfmap to resolve that address back to a Java method name.
- Stacks ship to the backend where they're aggregated into the FlameGraph.
CodeXray does not ship or require async-profiler. It uses the JVM's built-in attach mechanism (jattach-style) and the JVM's own PrintAssembly / perfmap support — which every HotSpot JVM includes. This means no sidecar, no JAR to bundle, no extra library on the classpath.
Requirements
JVM
- HotSpot / OpenJDK JVM — Oracle JDK, Adoptium Temurin, Amazon Corretto, Azul Zulu, and most mainstream distributions.
- Java 8+ — older Javas may work but are not actively tested.
-XX:+PreserveFramePointer— required for JIT method-name resolution (see below).
Host
- The Node Agent running on the same host as the JVM process
- Linux kernel 4.16+ (same as eBPF tracing)
Container access
If your Java app runs in a container, the Node Agent handles namespace crossing automatically — nothing extra to configure on the container.
Enabling -XX:+PreserveFramePointer
This one flag is the difference between a readable Java FlameGraph and a wall of hex addresses. Add it wherever your JVM options live.
Command line
java -XX:+PreserveFramePointer -jar your-application.jar
As a JAVA_OPTS environment variable
export JAVA_OPTS="-XX:+PreserveFramePointer"
java $JAVA_OPTS -jar your-application.jar
In a Dockerfile
FROM eclipse-temurin:17-jre
COPY target/app.jar /app/app.jar
ENV JAVA_OPTS="-XX:+PreserveFramePointer"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
In a Kubernetes Pod spec
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-service
spec:
template:
spec:
containers:
- name: orders
image: your-registry/orders:latest
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:+PreserveFramePointer"
JAVA_TOOL_OPTIONS is the least intrusive — most frameworks (Spring Boot, Quarkus, Micronaut, Dropwizard, Tomcat) respect it without any additional wiring.
Spring Boot with JIB / native packaging
Pass the flag through the standard mechanism your build tool provides — -Dspring-boot.run.jvmArguments="-XX:+PreserveFramePointer" for Maven, bootRun { jvmArgs += '-XX:+PreserveFramePointer' } for Gradle.
What Gets Profiled
✓ Included
- JIT-compiled Java methods (with
-XX:+PreserveFramePointerenabled) - JNI / native code called from Java
- JVM internal frames — GC threads, compiler threads, JIT work, safepoints
✗ Not yet covered
- Memory allocations / heap profile — not available via eBPF
- JVM GC pauses as first-class telemetry — use the Runtime tab on the application page for GC metrics
- Lock contention / monitor wait profile — not captured
For a richer runtime view of a JVM service (GC stats, memory pools, threadpool state), the Runtime tab on the application page pairs well with the CPU FlameGraph here.
Verifying It's Working
- Confirm the flag is active: on the running JVM, check
jinfo -flag PreserveFramePointer <pid>— should show+PreserveFramePointer. In a container:docker exec <container> jinfo -flag PreserveFramePointer 1. - Node Agent logs: the agent logs when it successfully attaches to a JVM and dumps a perfmap. Look for
perfmaporjattachmessages on the agent. - In the UI: open the service → Profiling tab. The FlameGraph should show Java method names like
com.yourcompany.service.OrderProcessor.process, not hex addresses.
If most frames are hex addresses or <unknown>:
- The flag isn't set. Verify with
jinfoas above. - The JVM is too old or not HotSpot — check your distribution.
- The agent can't write to
/tmpinside the container. Some hardened runtimes mount/tmpread-only — give the container a writable/tmpif possible.
Memory Profiling Status
Memory profiles for Java applications are not currently available. eBPF can sample CPU stacks but can't observe Java heap allocations without JVMTI-level cooperation. For memory, the Runtime tab on the application page gives you heap usage, GC pauses, and memory-pool state, which covers most operational memory-investigation needs.
Next Steps
- Overview — FlameGraph reading and comparison mode
- eBPF-based Profiling — the underlying mechanism
- CodeXray Node Agent — install the agent