Java SE-8 使用 JNI DefineClass 注入(inject)类导致 NoClassDefFound

标签 java jvm java-native-interface

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

第 1 步:使用 LoadLibrary(C++ 代码)加载 jvm dll

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

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

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

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

mySingleton = MySingletonClass.getInstance();

此行导致 NoClassDefFound 异常,我很难解决。 请注意,相同的代码适用于 Java SE-7,因此这可能是由于对垃圾收集类的方式进行了一些更改或由于类加载器的可见性发生了变化 - 我只是停留在如何调试它并找到原因。

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

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

最佳答案

我认识 Andrea 并找到了原因和解决方案,但不知道为什么 Java 8 会发生这种变化。

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

我创建了一个非常简单的 C++ 控制台应用程序来启动 JVM,一个简单的 Agent,它在调用 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,它还没有被定义。但即使在我们定义了它之后,似乎也会出现一些链式 react ,导致应用程序的其余部分找不到该类。

因此,解决方案是确保我们的 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 个类,而以前没有,以及这是否应该发生。

关于Java SE-8 使用 JNI DefineClass 注入(inject)类导致 NoClassDefFound,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30622474/

相关文章:

java - 更新原始联系人

java - 枚举的通用输入参数编译错误

java - 将 GMT 作为时区而不是 HST 或 PDT

java - 具有相同字符串值的两个不同变量引用相同的内存地址

java.lang 在 JNI 中调用 BluetoothAdapter.getDefaultAdapter() 时抛出 UNsatisfiedLinkError

java - 使用内存映射文件作为交换文件?

java - 我应该在这里使用哪个集合?

java - HotSpot 中 Runnable 的成本

java - JVM 命令行选项

java - 通过 JNI 将数据从 C 传递到 Java 的快速方法