I am writing an application using the JVMTI. I am trying to instrument the bytecode: by injecting method calls on every method entry.
I know how to do that, but the problem is in the instrument class, say it's called Proxy
, which I load using the JNI function DefineClass. My Proxy
has a few dependencies in Java Class Library, currently just java.lang.ThreadLocal<Boolean>
.
Now, say I have this, where inInstrumentMethod
is a plain boolean
:
public static void onEntry(int methodID)
{
if (inInstrumentMethod) {
return;
} else {
inInstrumentMethod = true;
}
System.out.println("Method ID: " + methodID);
inInstrumentMethod = false;
}
The code compiles and works. However, if I make inInstrumentMethod
a java.lang.ThreadLocal<Boolean>
, I get a NoClassDefFoundError. The code:
private static ThreadLocal<Boolean> inInstrumentMethod = new ThreadLocal<Boolean>() {
@Override protected Boolean initialValue() {
return Boolean.FALSE;
}
};
public static void onEntry(int methodID)
{
if (inInstrumentMethod.get()) {
return;
} else {
inInstrumentMethod.set(true);
}
System.out.println("Method ID: " + methodID);
inInstrumentMethod.set(false);
}
My guess is that the dependencies have not been resolved correctly, and java.lang.ThreadLocal
was not loaded (and thus could not be found). The question is, then, how do I force Java to load java.lang.ThreadLocal
? I don't think I could use DefineClass
in this case; is there an alternative?
I don’t think that there is a problem resolving the standard class java.lang.ThreadLocal
, but rather with the inner class extending it, generated by
new ThreadLocal<Boolean>() {
@Override protected Boolean initialValue() {
return Boolean.FALSE;
}
};
Solving this via DefineClass
might indeed be impossible due to the circular dependency between the inner and outer class, so there’s no order which allows to define them, unless you have a full-fledged ClassLoader
that returns the classes on demand.
The simplest solution is to avoid the generation of an inner class at all, which is possible with Java 8:
private static ThreadLocal<Boolean> inInstrumentMethod
= ThreadLocal.withInitial(() -> Boolean.FALSE);
If you use a version prior to Java 8, you can’t use it that way, so the best solution in that case, is to rewrite the code to accept the default value of null
as initial value, eliminating the need to specify a different initial value:
private static ThreadLocal<Boolean> inInstrumentMethod = new ThreadLocal<>();
public static void onEntry(int methodID)
{
if (inInstrumentMethod.get()!=null) {
return;
} else {
inInstrumentMethod.set(true);
}
System.out.println("Method ID: " + methodID);
inInstrumentMethod.set(null);
}
You could also convert that anonymous inner class to a top level class. Since then, that class has no dependency to what was formerly its outer class, defining that subtype of ThreadLocal
first, before defining the class using it, should solve the issue.