docs/Troubleshooting.md
This guide helps you diagnose and resolve common BTrace issues.
Error message:
com.sun.tools.attach.AttachNotSupportedException: Unable to attach to target VM
Causes and solutions:
Permission mismatch
# Check process owner
ps aux | grep <PID>
# Run as correct user
sudo -u <username> btrace <PID> script.java
Attach mechanism disabled or restricted
For JDK 8-20:
-XX:+DisableAttachMechanismFor JDK 21+ (JEP 451):
WARNING: A Java agent has been loaded dynamically-XX:+EnableDynamicAgentLoading to target JVM startup to suppress warnings:
java -XX:+EnableDynamicAgentLoading -jar your-application.jar
-XX:+EnableDynamicAgentLoading flag will be required to use BTrace's attach mode.java -javaagent:/path/to/btrace-agent.jar=script=YourScript.class -jar your-application.jar
JRE instead of JDK
JAVA_HOME points to it# Verify JDK installation
which javac
echo $JAVA_HOME
Process in container/namespace
# For Docker
docker exec -it <container> btrace <PID> script.java
SELinux/AppArmor restrictions
# Check SELinux
getenforce
# Temporarily disable (not recommended for production)
sudo setenforce 0
Error message:
java.net.ConnectException: Connection refused
Solutions:
btrace -p 3030 <PID> script.javaError message:
error: cannot find symbol
Common causes:
Missing imports
// Wrong
@BTrace
public class MyTrace {
@OnMethod(...)
public static void handler() {
println("test"); // ERROR: cannot find symbol
}
}
// Correct
import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
public class MyTrace {
@OnMethod(...)
public static void handler() {
println("test"); // OK
}
}
Wrong package for annotations
// Old (pre-2.0)
import com.sun.btrace.annotations.*;
// New (2.0+)
import org.openjdk.btrace.core.annotations.*;
Classpath issues
btrace -cp /path/to/lib.jar <PID> script.java
Error message:
BTrace : verification failed
BTrace enforces safety restrictions. Common violations:
Wrong:
@OnMethod(...)
public static void handler() {
new Thread(() -> {}).start(); // ERROR: Cannot create threads
}
Right:
@OnMethod(...)
public static void handler() {
// Use @OnTimer instead
}
@OnTimer(1000)
public static void periodic() {
// Executes every second
}
Wrong:
private static Object lock = new Object();
@OnMethod(...)
public static void handler() {
synchronized (lock) { // ERROR: Cannot use synchronization
// ...
}
}
Right:
import static org.openjdk.btrace.core.BTraceUtils.Atomic.*;
private static AtomicInteger counter = newAtomicInteger(0);
@OnMethod(...)
public static void handler() {
incrementAndGet(counter); // Thread-safe without synchronization
}
Wrong:
@OnMethod(...)
public static void handler() {
// ERROR: All loops forbidden in safe mode
while (condition) { }
for (int i = 0; i < 100; i++) { }
do { } while (condition);
for (String s : collection) { }
}
Right:
@OnMethod(...)
public static void handler() {
// Use BTraceUtils methods instead
// For iteration, use unsafe mode (-u flag) if absolutely necessary
}
Note: ALL loop constructs (for, while, do-while, enhanced for) are forbidden in safe mode. Use -u (unsafe mode) only in controlled environments if loops are required.
Wrong:
@OnMethod(...)
public static void handler() {
FileWriter fw = new FileWriter("out.txt"); // ERROR: No file I/O
}
Right:
@OnMethod(...)
public static void handler() {
println("Output"); // Use BTraceUtils output functions
}
// Or run in unsafe mode
// btrace -u <PID> script.java
Wrong:
@OnMethod(...)
public static void handler() {
MyOtherClass.someMethod(); // ERROR: Cannot call external class methods
System.out.println("test"); // ERROR: Cannot call System methods
}
Right:
@BTrace
public class MyTrace {
@OnMethod(...)
public static void handler() {
// Call helper methods within same BTrace class - OK
myCustomMethod();
// Use BTraceUtils methods
println(str(timeMillis()));
}
private static void myCustomMethod() {
// Helper methods in same class are allowed
println("Helper method called");
}
}
Note: You CAN call private static methods within the same BTrace class. You CANNOT call methods from external classes (except BTraceUtils).
Wrong:
@OnMethod(...)
public static void handler() {
try {
// ...
} catch (Exception e) { // ERROR: Cannot catch exceptions
// ...
}
}
Right:
// BTrace handles exceptions automatically
@OnMethod(...)
public static void handler() {
// Any exception here is caught and logged by BTrace
}
// Or use @OnError to track exceptions
@OnMethod(..., location = @Location(Kind.ERROR))
public static void onError(Throwable t) {
println("Exception: " + str(t));
}
Diagnostic checklist:
Verify method is being called
// Add entry point logging
@OnMethod(clazz = "com.example.MyClass", method = "<init>")
public static void onInit() {
println("Constructor called - script is working!");
}
Check class and method names
# Class names are case-sensitive and must be fully qualified
# Wrong
@OnMethod(clazz = "MyClass", method = "process")
# Right
@OnMethod(clazz = "com.example.pkg.MyClass", method = "process")
Test with wildcard patterns
// Too specific - might not match
@OnMethod(clazz = "com.example.MyClass", method = "processData")
// Broader - helps identify the issue
@OnMethod(clazz = "/com\\.example\\..*/", method = "/.*/")
public static void catchAll() {
println("Matched something!");
}
Verify regex escaping
// Wrong - dots match any character
@OnMethod(clazz = "/com.example..*/")
// Right - dots are escaped
@OnMethod(clazz = "/com\\.example\\..*/")
Check for inner classes
// Inner classes use $ separator
@OnMethod(clazz = "com.example.Outer$Inner", method = "method")
Enable verbose output
btrace -v <PID> script.java
Verify instrumention occurred
# Dump instrumented classes
btrace -d /tmp/btrace-dump <PID> script.java
# Check if classes were modified
ls -la /tmp/btrace-dump/
Error message:
No methods matched for probe: ...
Solutions:
Use jcmd to find exact class names
# List loaded classes
jcmd <PID> VM.classes | grep -i myclass
# Find specific methods
jcmd <PID> VM.class_hierarchy | grep -A 5 MyClass
Verify BTrace is retransforming loaded classes
Instrumentation.retransformClasses()# Some classes cannot be retransformed (e.g., native methods, JVM internals)
# Use -v flag to see which classes are being instrumented
btrace -v <PID> script.java
Check class loader hierarchy
// Match classes loaded by any classloader
@OnMethod(clazz = "+com.example.MyClass", method = "method")
// ^ plus sign matches subclasses and all classloaders
Verify method signature for overloaded methods
// Multiple methods with same name - need signature
@OnMethod(clazz = "com.example.MyClass",
method = "process",
type = "void (java.lang.String, int)")
Symptoms:
Solutions:
Use sampling
@Sampled(kind = Sampled.Sampler.Adaptive)
@OnMethod(...)
public static void handler() {
// Only executes on sampled invocations
}
Add level filtering
@OnMethod(clazz = "...", enableAt = @Level(">=2"))
public static void heavyHandler() {
// Only active when level >= 2
}
// Control at runtime:
// Press Ctrl-C in btrace console, type: level 0
Avoid tracing high-frequency methods
// BAD - called millions of times per second
@OnMethod(clazz = "java.lang.String", method = "charAt")
// GOOD - application-specific methods
@OnMethod(clazz = "com.example.Service", method = "handleRequest")
Minimize instrumentation scope
// Too broad - matches thousands of methods
@OnMethod(clazz = "/java\\..*/", method = "/.*/")
// Focused - matches specific target
@OnMethod(clazz = "com.example.Service", method = "slowMethod")
Reduce output volume
// Bad - prints every invocation
@OnMethod(...)
public static void handler() {
println("Called");
}
// Better - periodic summary
private static AtomicInteger count = newAtomicInteger(0);
@OnMethod(...)
public static void handler() {
incrementAndGet(count);
}
@OnTimer(5000)
public static void report() {
println("Calls in last 5s: " + str(get(count)));
set(count, 0);
}
Use aggregations instead of individual logs
private static Aggregation duration =
Aggregations.newAggregation(AggregationFunction.QUANTIZE);
@OnMethod(..., location = @Location(Kind.RETURN))
public static void onReturn(@Duration long d) {
Aggregations.addToAggregation(duration, d / 1000000);
}
@OnTimer(10000)
public static void printStats() {
Aggregations.printAggregation("Durations", duration);
Aggregations.clearAggregation(duration);
}
Error message:
Error opening zip file or JAR manifest missing : /path/to/btrace-agent.jar
Solutions:
# Verify JAR exists
ls -la /path/to/btrace-agent.jar
# Use absolute path
java -javaagent:/absolute/path/to/btrace-agent.jar=script=Script.class MyApp
# Or set BTRACE_HOME
export BTRACE_HOME=/path/to/btrace
java -javaagent:$BTRACE_HOME/libs/btrace-agent.jar=script=Script.class MyApp
Error message:
ClassNotFoundException: MyTrace
Solutions:
# Specify absolute path to script
java -javaagent:btrace-agent.jar=script=/absolute/path/MyTrace.class MyApp
# Or add to classpath
java -javaagent:btrace-agent.jar=script=MyTrace.class \
-cp /path/to/scripts:app.jar MyApp
Error message:
UnsupportedClassVersionError
Solutions:
JAVA_HOME points to correct versionError message:
IllegalAccessError: class X cannot access class Y
Solutions:
# Add required --add-opens flags
btrace --add-opens java.base/jdk.internal.misc=ALL-UNNAMED <PID> script.java
# Or add to target JVM startup
java --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
MyApp
Error: Scripts written for BTrace 1.x fail with 2.x
Solution: Update imports:
// Old (1.x)
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
// New (2.x)
import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
btrace -v <PID> script.java
btrace -d /tmp/btrace-classes <PID> script.java
Then examine with:
javap -c /tmp/btrace-classes/com/example/MyClass.class
@OnMethod(clazz = "com.example.MyClass", method = "myMethod")
public static void handler(@ProbeClassName String clazz,
@ProbeMethodName String method) {
println("Class: " + clazz);
println("Method: " + method);
println("Thread: " + threadName());
println("Stack:");
jstack(5); // Print 5 stack frames
}
Start simple and add complexity:
// Step 1: Verify attachment
@OnTimer(1000)
public static void heartbeat() {
println("BTrace alive");
}
// Step 2: Verify class matching
@OnMethod(clazz = "com.example.MyClass", method = "/.*/")
public static void anyMethod() {
println("Matched");
}
// Step 3: Add specific logic
@OnMethod(clazz = "com.example.MyClass", method = "specificMethod")
public static void specific(String arg) {
println("Arg: " + arg);
}
Problem: Need to find Java process ID inside a pod
Solutions:
# List Java processes in pod
kubectl exec <pod-name> -- jps
# Or use ps
kubectl exec <pod-name> -- ps aux | grep java
# For multi-container pods, specify container
kubectl exec <pod-name> -c <container-name> -- jps
Problem: Cannot attach to process in different container
Solution: Kubernetes pods with multiple containers have separate process namespaces by default.
# Enable process namespace sharing
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
shareProcessNamespace: true # Required for cross-container attachment
containers:
- name: app
image: myapp:latest
- name: btrace-sidecar
image: btrace:latest
Note: With shareProcessNamespace: true, all containers can see each other's processes.
Problem: BTrace fails with port already in use
Error:
java.net.BindException: Address already in use
Cause: BTrace uses port 2020 by default; multiple BTrace instances or port conflicts
Solutions:
# Use different port
kubectl exec <pod> -- btrace -p 3030 <PID> script.java
# Check port usage
kubectl exec <pod> -- netstat -an | grep 2020
Problem: Pod Security Policy blocks BTrace attachment
Error:
Operation not permitted
Common causes:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
securityContext:
# May need to adjust based on policy
runAsNonRoot: true
capabilities:
add:
- SYS_PTRACE # Required for process attachment
# Check if SELinux is blocking
kubectl exec <pod> -- getenforce
# Check AppArmor profile
kubectl exec <pod> -- cat /proc/self/attr/current
Solutions:
SYS_PTRACE capability if allowed by security policyProblem: BTrace requires JDK but only JRE in container
Error:
Error: tools.jar not found
Solutions:
Option 1: Use JDK base image
# Instead of JRE
FROM openjdk:11-jre
# Use JDK
FROM bellsoft/liberica-openjdk-debian:11-cds
Option 2: Install JDK in running pod (temporary)
kubectl exec <pod> -- apt-get update && apt-get install -y openjdk-11-jdk
Option 3: Sidecar with JDK
spec:
shareProcessNamespace: true
containers:
- name: app
image: myapp-jre:latest # Can use JRE
- name: btrace
image: bellsoft/liberica-openjdk-debian:11-cds # Sidecar has JDK
Istio/Linkerd:
BTrace works with service meshes but some considerations apply:
Traffic Interception:
mTLS:
Resource Limits:
# BTrace overhead may trigger limits
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m" # Increase if BTrace causes throttling
memory: "512Mi" # Increase if needed
Pattern: Store scripts in ConfigMap for easy distribution
apiVersion: v1
kind: ConfigMap
metadata:
name: btrace-scripts
data:
TraceMethod.java: |
import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
// Classname 'TraceMethod' must correspond to the file name 'TraceMethod.java'
public class TraceMethod {
@OnMethod(clazz = "com.example.Service", method = "process")
public static void onProcess() {
println("Method called");
}
}
---
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: scripts
mountPath: /btrace-scripts
volumes:
- name: scripts
configMap:
name: btrace-scripts
Usage:
kubectl exec <pod> -- btrace <PID> /btrace-scripts/TraceMethod.java
Pattern: Trace all pods in a deployment
#!/bin/bash
# trace-deployment.sh
DEPLOYMENT=$1
SCRIPT=$2
# Get all pod names for deployment
PODS=$(kubectl get pods -l app=$DEPLOYMENT -o jsonpath='{.items[*].metadata.name}')
for POD in $PODS; do
echo "Attaching to $POD..."
kubectl exec $POD -- btrace 1 $SCRIPT &
done
wait
echo "All traces complete"
Usage:
./trace-deployment.sh myapp /tmp/trace.java
Issue: BTrace doesn't persist across pod restarts
Solutions:
kubectl exec <pod> -- btrace -o /data/trace.log <PID> script.java
// Use StatSD integration
@OnMethod(...)
public static void handler() {
Statsd.send("metric.name", value);
}
spec:
containers:
- name: app
image: myapp:latest
env:
- name: JAVA_TOOL_OPTIONS
value: "-javaagent:/opt/btrace/lib/btrace-agent.jar=script=/scripts/trace.class"
AWS EKS:
Google GKE:
SYS_PTRACE capabilityAzure AKS:
OpenShift:
oc adm policy add-scc-to-user anyuid -z default
For more K8s deployment patterns, see Getting Started: K8s and FAQ: Microservices.
Native methods:
Sensitive classes (to avoid infinite recursion):
java.lang.Object, String, ThreadLocal, Integer, Numberjava.lang.instrument.*, java.lang.invoke.*, java.lang.ref.*java.util.concurrent.locks.LockSupport, AbstractQueuedSynchronizer, etc.jdk.internal.*, sun.invoke.*org.openjdk.btrace.*Classes annotated with @BTrace (BTrace scripts themselves)
Note: Classes already transformed by other agents CAN be retransformed. Boot classpath and JDK classes CAN be instrumented (except the sensitive classes listed above).
Windows:
\\ or /macOS:
csrutil disable (not recommended for production)Linux:
/proc/sys/kernel/yama/ptrace_scopeecho 0 | sudo tee /proc/sys/kernel/yama/ptrace_scopeIf issues persist:
Check BTrace version
btrace --version
Search existing issues
Provide details when asking for help:
java -version)Community channels: