带有JNI DefineClass的Java SE-8注入类导致NoClassDefFound

时间:2015-06-03 14:05:51

标签: java jvm java-native-interface

我正在开发一个应用程序,我在自定义启动器中以下列方式直接将一些类注入JVM:

步骤1:使用LoadLibrary(C ++代码)

加载jvm dll

第2步:实际注入类:(注意这使用引导类加载器)

jclass ljcl_LoadedClass = env->DefineClass(className, NULL, classBytes, classSize);

第3步:启动应用程序 jniEnvironment_->CallStaticVoidMethod实际调用main方法并启动应用程序。

稍后在Java代码中,我尝试使用注入的类

mySingleton = MySingletonClass.getInstance();

这一行导致NoClassDefFound异常,我很难解决。 请注意,相同的代码正在使用Java SE-7,因此可能是由于对类的垃圾收集方式或类加载器可见性的更改有所改变 - 我只是坚持如何调试它并查找原因。

另一件有趣的事情是在C ++代码中(在启动应用程序之前,但是加载了JVM dll后,下面的代码实际上找到了类,我甚至可以调用getInstance方法:

jclass olDecryptorClass = jniEnvironment_->FindClass("com/foo/bar/MySingletonClass");

1 个答案:

答案 0 :(得分:1)

我知道Andrea已经发现了原因和解决方案,但不知道为什么在Java 8中发生了这种变化。

我在使用Java代理的原始问题中缺少关键信息,并且代理使用导致NoClassDefFoundError的类。

我创建了一个非常简单的C ++控制台应用程序来启动JVM,一个简单的代理在调用premain()方法时输出到控制台“Agent Started”,然后通过我们的覆盖输出“Loading class:className” ClassFileTransformer.transform()方法。

使用Java 7时,我们在调试时得到以下内容:

C ++步骤

options[1].optionString = "-javaagent:simple-agent.jar";
errorCode = createJavaVM(&javaVM, (void**)&jniEnv, &initArgs);

控制台

Agent Started

C ++步骤

jclass ljcl_LoadedClass = jniEnv->DefineClass(CLASS_NAME, NULL, ljby_ClassBytes, lji_Size);

控制台

Loading class: com/foo/bar/JVMDefinedClass

C ++步骤

jniEnv->CallStaticVoidMethod(helloWorldClass, mainMethod, mainArgs);

控制台

Loading class: com/for/bar/HelloWorld
Hello World!

在Java 8中,这变为:

C ++步骤

options[1].optionString = "-javaagent:simple-agent.jar";
errorCode = createJavaVM(&javaVM, (void**)&jniEnv, &initArgs);

控制台

Agent Started
Loading class: java/lang/invoke/MethodHandleImpl
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 4 more
Caused by: java.lang.ClassNotFoundException:
... 8 more
Loading class: java/lang/invoke/MethodHandleImpl$1
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/invoke/MethodHandleImpl$2
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/util/function/Function
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/invoke/MethodHandleImpl$3
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/invoke/MethodHandleImpl$4
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/ClassValue
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/ClassValue$Entry
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 6 more
Loading class: java/lang/ClassValue$Identity
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 7 more
Loading class: java/lang/ClassValue$Version
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 7 more
Loading class: java/lang/invoke/MemberName$Factory
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: java/lang/invoke/MethodHandleStatics
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 4 more
Loading class: java/lang/invoke/MethodHandleStatics$1
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 5 more
Loading class: sun/misc/PostVMInitHook
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
... 3 more

C ++步骤

jclass ljcl_LoadedClass = jniEnv->DefineClass(CLASS_NAME, NULL, ljby_ClassBytes, lji_Size);

控制台

Loading class: com/foo/bar/JVMDefinedClass

C ++步骤

jniEnv->CallStaticVoidMethod(helloWorldClass, mainMethod, mainArgs);

控制台

Loading class: com/objectiflune/simple/TestClass
java.lang.NoClassDefFoundError: com/foo/bar/JVMDefinedClass
at com.foo.bar.ClassTransformer.transform(ClassTransformer.java:41)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
... 13 more
Hello World!

很容易理解为什么最初的14个java/lang类无法找到我们的JVMDefinedClass,它尚未定义。但即使在我们定义它之后,似乎也会产生一些影响,导致应用程序的其余部分找不到类。

因此,解决方案是确保我们的ClassFileTransformer.transform()方法忽略这些(和任何)Java系统类:

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
    ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

    if (className.equals("com/foo/bar/JVMDefinedClass")
     || className.startsWith("com/sun")
     || className.startsWith("java/")
     || className.startsWith("javax/")
     || className.startsWith("org/ietf")
     || className.startsWith("org/jcp")
     || className.startsWith("org/omg")
     || className.startsWith("org/w3c")
     || className.startsWith("org/xml")
     || className.startsWith("sun/")
     || className.startsWith("com/oracle/")
     || className.startsWith("jdk/")
     || className.startsWith("oracle/")
     || className.startsWith("javafx/")
    ) return null;
//... rest of method ...//

既然我们不处理这14个类,则不会抛出NoClassDefFoundError并且应用程序按预期运行。

我唯一不理解的是为什么Java 8中的JVM现在通过代理发送这14个类,而不是之前,以及是否应该这样做。