【问题标题】:Loading multiple versions of Java classes that use native code加载使用本机代码的多个版本的 Java 类
【发布时间】:2016-10-13 16:51:33
【问题描述】:

如果你想加载一个类的多个版本,如果它们实现了一个共享接口并且在单独的 JAR 中,你可以这样做,using a separate class loader for each version

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

但如果你两者都做,会奏效吗?如果 JAR 的两个版本都调用本机代码,并且都包含不同版本的共享库,会发生什么情况?

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

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

【问题讨论】:

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


    【解决方案1】:

    检查 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. POSIX dlopen 系统调用的Linux variant 默认具有RTLD_LOCAL 行为,因此共享库加载了RTLD_LOCAL 语义。
    10. RTLD_LOCAL 语义是已加载库中的符号可用于后续加载库的(自动)符号解析。即符号不进入全局命名空间,不同的库可以定义相同的符号而不会产生冲突。共享库could even have identical content without problems
    11. 因此,由不同类加载器加载的不同共享库是否定义相同符号(对于本机方法具有相同名称的 extern 函数)并不重要:JRE 和 JVM 一起避免名称冲突。

    本机函数查找

    这可确保共享库的多个版本不会产生名称冲突。但是 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. 通过从NativeLookup::lookup 调用methodHandle::set_native_function 将JNI 函数的地址记录在methodHandle 中。
    4. NativeLookup::lookup 间接代表NativeLookup::lookup_style
    5. NativeLookup::lookup_style 委托给 Java 包私有静态方法 ClassLoader.findNative
    6. ClassLoader.findNative 遍历由ClassLoader.loadLibrary0 设置的ClassLoader.NativeLibrary 对象的列表(ClassLoader.nativeLibraries),以加载库的顺序。对于每个库,它委托给NativeLibrary.find 以尝试找到感兴趣的本机方法。虽然这个对象列表不是公开的,但JNI specification 要求 JVM“为每个类加载器维护一个加载的本机库列表”,因此所有实现都必须具有与此列表类似的内容。
    7. NativeLibrary.find 是本机方法。它只是委托给JVM_FindLibraryEntry
    8. JVM_FindLibraryEntry 委托给系统相关方法 os::dll_lookup
    9. os::dll_lookup 的 Linux 实现委托给 dlsym 系统调用以在共享库中查找函数的地址。
    10. 由于每个类加载器都维护自己的加载库列表,因此可以保证为本地方法调用的 JNI 代码将是正确的版本,即使不同的类加载器加载了不同版本的共享库。

    【讨论】:

      【解决方案2】:

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

      但是,如果您 - 正如您所说 - “使用不同的临时文件来存储共享库的副本”,那么无论文件的内容如何,​​这两个实际上都是不同的库(可能是二进制相同的,没关系)。所以没有问题。

      【讨论】:

      • 反对者愿意解释吗?如果有不清楚的地方,我愿意编辑。
      • 从 ClassLoader 类中的代码(我查看了 1.8)看来你是正确的(我赞成你的答案,不是我最初反对它)
      猜你喜欢
      • 2010-12-14
      • 2011-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 2013-06-09
      相关资源
      最近更新 更多