加载使用本机代码的多个Java类版本

时间:2016-10-13 16:51:33

标签: java java-native-interface shared-libraries classloader

如果要加载类的多个版本,如果它们实现共享接口并且位于单独的JAR using a separate class loader for each version中,则可以这样做。

如果您有一个调用本机代码的JAR,您可以在其JAR by extracting the shared library to a temporary file and then using System.load to load the library from the temporary file中存储本机代码的共享库(DLL)。

但如果你同时做到这两点,它会起作用吗?如果两个版本的JAR调用本机代码,并且两者都包含不同版本的共享库,会发生什么?

让我们假设两个JAR使用不同的临时文件来存储共享库的副本。但是共享库的两个版本具有本机代码,这些代码调用具有相同声明的本机(C)函数(但这些函数的实现是不同的)。 JVM /类加载器/ System.load是否会从Java代码委托给正确的本机代码?或者JVM会抱怨名称冲突吗?

如果该方案 失败,如何我使用使用本机代码的类的多个版本?

2 个答案:

答案 0 :(得分:3)

检查Open JDK 7实现,似乎是的,加载使用本机代码的多个版本的Java类工作:

库加载

关键信息是System.load如何表现?该方法的实现将取决于系统,但各种实现的语义应该相同。

  1. System.load委托包私有方法Runtime.load0
  2. Runtime.load0委托给包私有静态方法ClassLoader.loadLibrary
  3. ClassLoader.loadLibrary委托私有静态方法ClassLoader.loadLibrary0
  4. ClassLoader.loadLibrary0创建包私有内部类ClassLoader.NativeLibrary的对象,并委托其load方法。
  5. ClassLoader.NativeLibrary.load是一种本机方法,它委托给函数JVM_LoadLibrary
  6. JVM_LoadLibrary代表os::dll_load
  7. os::dll_load取决于系统。
  8. The Linux variant of os::dll_load委托dlopen系统调用,提供RTLD_LAZY选项。
  9. 默认情况下,Linux variant系统调用的POSIX dlopen具有RTLD_LOCAL行为,因此共享库加载了RTLD_LOCAL语义。
  10. RTLD_LOCAL语义是加载的库中的符号可用于后续加载的库的(自动)符号解析。也就是说,符号不会进入全局命名空间,并且不同的库可以定义相同的符号而不会产生冲突。共享库could even have identical content without problems
  11. 因此,由不同类加载器加载的不同共享库定义相同的符号(本机方法具有相同的extern函数名称)并不重要:JRE和JVM一起避免名称冲突。 / LI>

    本机功能查找

    这可确保共享库的多个版本不会生成名称冲突。但OpenJDK如何确保将正确的 JNI代码用于本机方法调用?

    1. procedure followed by the JVM to call a native method相当冗长,但它全部包含在一个函数SharedRuntime::generate_native_wrapper中。但是,最终需要知道要调用的JNI函数的地址。
    2. 该包装函数使用methodHandle C ++对象,根据需要从methodHandle::critical_native_function()methodHandle::native_function()获取JNI函数的地址。
    3. 通过methodHandle致电methodHandle::set_native_functionNativeLookup::lookup会记录JNI功能的地址。
    4. NativeLookup::lookup代表,间接地,NativeLookup::lookup_style
    5. NativeLookup::lookup_style委托Java包 - 私有静态方法ClassLoader.findNative
    6. ClassLoader.findNative按照ClassLoader.nativeLibraries设置的ClassLoader.NativeLibrary个对象的列表(ClassLoader.loadLibrary0)按照加载库的顺序进行迭代。对于每个库,它委托NativeLibrary.find尝试查找感兴趣的本机方法。虽然此对象列表不公开,但JNI specification要求JVM“维护每个类加载器的已加载本机库列表”,因此所有实现都必须具有与此列表类似的内容。
    7. NativeLibrary.find是一种原生方法。它只是委托给JVM_FindLibraryEntry
    8. JVM_FindLibraryEntry委托系统相关方法os::dll_lookup
    9. os::dll_lookup的Linux实现委托给dlsym系统调用,以查找共享库中函数的地址。
    10. 因为每个类加载器都维护自己的加载库列表,所以即使不同的类加载器加载不同版本的共享库,也可以保证调用本机方法的JNI代码是正确的版本。 / LI>

答案 1 :(得分:-1)

如果您尝试在不同的类加载器中加载相同的库,您将获得UnsatisfiedLinkError消息" Native Library:...已经加载到另一个类加载器& #34 ;.当类加载器被垃圾收集(https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#compiling_loading_and_linking_native_methods)时,这可能与调用库的卸载方法的VM有关。

但是,如果您 - 正如您所说 - "使用不同的临时文件来存储共享库的副本"无论文件是什么,这两个都是有效的不同的库。内容(可能是二进制相同,无关紧要)。所以没有问题。