JNI 转换

要了解 JNI 局部引用管理和 GC,您必须了解与 JVM 相连的正在运行的线程的上下文。 每个线程都有一个运行时堆栈,其中对于每个方法调用都包含一个帧。 从 GC 透视图,每个堆栈都建立一个特定于线程的“根集”,包括堆栈中所有 JNI 局部引用的并集。

此图显示 JNI 应用程序的示例堆栈

正在运行的 VM 中的每个方法调用都会向堆栈添加(入栈)一个帧,正如每个返回移除(出栈)一个帧一样。 正在运行的堆栈中的每个调用点都可能具有以下某个类型的特征:

  • Java™ 到 Java (J2J)
  • 本机到本机 (N2N)
  • Java 到 Native (J2N)
  • 本机 Java (N2J)

只能在满足以下条件的线程中执行 N2J 转换:

  • 包含线程的进程必须包含使用 JNI Invocation API 启动的 JVM。
  • 线程必须“连接”到 JVM。
  • 线程必须至少将一个有效的局部或全局对象引用传递到 JNI。

J2J 和 N2N 转换

因为对象引用未作为 J2J 或 N2N 转换的一部分更改格式,因此 J2J 和 N2N 转换不会影响 JNI 局部引用管理。

如果 N2N 代码的任何部分获取许多本地引用而不立即返回到 Java ,那么会不必要地强调线程的本地引用容量。 如果由本机方法程序员显示管理局部引用,那么可避免此问题。

N2J 转换

要使本机代码在当前线程中调用 Java 代码 (N2J) ,必须首先将线程连接到当前进程中的 JVM。

传递对象引用的每个 N2J 调用都必须使用 JNI 获取这些引用,因此,它们是有效的局部或全局 JNI 引用。 从调用返回的任何对象引用都是 JNI 局部引用。

J2N 调用

JVM 必须确保作为参数从 Java 传递到本机方法的对象以及本机代码创建的任何新对象仍可由 GC 访问。 为了处理 GC 需求,JVM 分配了一个较小的名为局部引用根集的专用存储区。

在以下情况下创建局部引用根集:

  • 线程第一次连接到 JVM(线程最外面的根集)。
  • 发生每个 J2N 转换。
JVM 初始化利用以下项针对 J2N 转换创建的根集:
  • 对调用程序对象或类的局部引用。
  • 对作为参数传递到本机方法的每个对象的局部引用。
在本机代码中创建的新的局部引用将添加到此 J2N 根集,除非您使用 PushLocalFrame JNI 函数创建新的局部帧。
缺省根集足够大,对于每个 J2N 转换可包含 16 个局部引用。 -Xcheck:jni 命令行选项会导致 JVM 监视 JNI 使用情况。 使用 -Xcheck:jni 时,如果运行时需要的本地引用超过 16 个,JVM 会写入一条警告消息。 如果收到该警告消息,请使用以下某个 JNI 函数来更为显式地管理局部引用:
  • NewLocalRef
  • DeleteLocalRef
  • PushLocalFrame
  • PopLocalFrame
  • EnsureLocalCapacity

J2N 返回

当本机代码返回到 Java 时,将释放由 J2N 调用创建的关联 JNI 局部引用 "根集"。

如果 JNI 局部引用是对某个对象的唯一引用,那么该对象将无法再访问,并且可考虑执行垃圾回收。 在这种情况下会自动触发垃圾回收,这将简化 JNI 程序员的内存管理工作。