我正在开发一个应用程序,在该应用程序中,我在自定义启动器中通过以下方式将一些类直接注入JVM:
步骤1:使用LoadLibrary (C++代码)加载jvm
步骤2:实际注入类:(请注意,这使用了引导类加载器)
jclass ljcl_LoadedClass = env->DefineClass(className, NULL, classBytes, classSize);
步骤3:启动应用程序jniEnvironment_->CallStaticVoidMethod
以实际调用main方法并启动应用程序。
稍后在Java代码中,我尝试使用注入的类
mySingleton = MySingletonClass.getInstance();
这一行导致了一个NoClassDefFound异常,我很难解决这个问题。请注意,相同的代码适用于Java SE-7,因此这可能是由于类的垃圾收集方式或类加载器可见性方面的一些更改-我只是卡在如何调试它并找出原因上。
另一件有趣的事情是,在C++代码中(在启动应用程序之前,但是在加载了JVM的情况下,下面的代码实际上找到了类,我甚至能够调用getInstance方法:
jclass olDecryptorClass = jniEnvironment_->FindClass("com/foo/bar/MySingletonClass");
发布于 2015-06-22 08:23:23
我知道Andrea,并发现了原因和解决方案,但不知道为什么Java 8会发生这种变化。
在最初的问题中,缺少了一条关键信息,即使用了Java代理,并且代理使用了导致NoClassDefFoundError
的类。
我创建了一个非常简单的C++控制台应用程序来启动JVM,一个简单的代理,当premain()
方法被调用时,它输出到控制台“代理启动”,然后通过我们覆盖的ClassFileTransformer.transform()
方法输出“装入类: className”。
当使用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 system类:
@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个类,而以前不是这样的,以及这种情况是否应该发生。
https://stackoverflow.com/questions/30622474
复制相似问题