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

Rae*_*ald 2 java java-native-interface shared-libraries classloader

如果您想要加载一个类的多个版本,如果它们实现了共享接口并且位于单独的 JAR 中,则可以为每个版本使用单独的类加载器来执行此操作。

如果您有一个调用本机代码的 JAR,则可以将本机代码的共享库 (DLL) 存储在其 JAR 中,方法是将共享库提取到临时文件,然后使用System.load从临时文件加载该库

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

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

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

Rae*_*ald 5

检查 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是一个本机方法,它委托给 function JVM_LoadLibrary
  6. JVM_LoadLibrary代表到os::dll_load.
  7. os::dll_load是系统相关的。
  8. Linux 变体os::dll_load委托给dlopen系统调用,提供RTLD_LAZY选项。
  9. POSIX系统调用的Linux变体默认具有行为,因此共享库加载有语义。dlopenRTLD_LOCALRTLD_LOCAL
  10. RTLD_LOCAL语义是加载的库中的符号不​​可用于后续加载的库的(自动)符号解析。也就是说,符号不进入全局命名空间,不同的库可以定义相同的符号而不会产生冲突。共享库甚至可以具有相同的内容而不会出现问题
  11. 因此,由不同类加载器加载的不同共享库定义相同的符号(本机方法具有相同的函数名称)并不重要extern:JRE 和 JVM 一起避免名称冲突。

本机函数查找

这确保了共享库的多个版本不会产生名称冲突。但是 OpenJDK 如何确保本地方法调用使用正确的JNI 代码呢?

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