java - 了解 JNI 参数的安全访问

标签 java c++ c jvm java-native-interface

我正在研究 HotSpot 在 JNI 代码运行时如何执行垃圾收集和/或堆压缩。

在 Java 中可以随时移动对象似乎是常识。我试图明确地理解 JNI 是否受到影响垃圾收集的影响。有许多 JNI 函数可以显式地防止垃圾收集;如GetPrimitiveArrayCritical .如果引用确实是不稳定的,那么存在这样的函数是有道理的。但是,如果他们不是,那就没有意义了。

关于这个主题似乎有大量相互矛盾的信息,我正在尝试对其进行整理。

JNI code runs in a safepoint and can continue running, unless it calls back into Java or calls some specific JVM methods, at which point it may be stopped to prevent leaving the safepoint (thanks Nitsan for the comments).

What mechanism JVM use to block threads during stop-the-world pause



以上让我认为垃圾收集将与 JNI 代码同时运行。那不可能是安全的,对吧?

To implement local references, the Java VM creates a registry for each transition of control from Java to a native method. A registry maps nonmovable local references to Java objects, and keeps the objects from being garbage collected. All Java objects passed to the native method (including those that are returned as the results of JNI function calls) are automatically added to the registry. The registry is deleted after the native method returns, allowing all of its entries to be garbage collected.

https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp16789



好的,所以 local引用是不可移动的,但这并没有说明压缩。

The JVM must ensure that objects passed as parameters from Java™ to the native method and any new objects created by the native code remain reachable by the GC. To handle the GC requirements, the JVM allocates a small region of specialized storage called a "local reference root set".

A local reference root set is created when:

  • A thread is first attached to the JVM (the "outermost" root set of the thread).
  • Each J2N transition occurs.

The JVM initializes the root set created for a J2N transition with:

  • A local reference to the caller's object or class.
  • A local reference to each object passed as a parameter to the native method.

New local references created in native code are added to this J2N root set, unless you create a new "local frame" using the PushLocalFrame JNI function.

http://www.ibm.com/support/knowledgecenter/en/SSYKE2_5.0.0/com.ibm.java.doc.diagnostics.50/diag/understanding/jni_transitions_j2n.html



好的,所以 IBM 将传递的对象存储在 local reference root set但它没有讨论内存压缩。这只是说对象不会被垃圾收集。

The GC might, at any time, decide it needs to compact the garbage-collected heap. Compaction involves physically moving objects from one address to another. These objects might be referred to by a JNI local or global reference. To allow compaction to occur safely, JNI references are not direct pointers to the heap. At least one level of indirection isolates the native code from object movement.

If a native method needs to obtain direct addressability to the inside of an object, the situation is more complicated. The requirement to directly address, or pin, the heap is typical where there is a need for fast, shared access to large primitive arrays. An example might include a screen buffer. In these cases a JNI critical section can be used, which imposes additional requirements on the programmer, as specified in the JNI description for these functions. See the JNI specification for details.

  • GetPrimitiveArrayCritical returns the direct heap address of a Java™ array, disabling garbage collection until the corresponding ReleasePrimitiveArrayCritical is called.
  • GetStringCritical returns the direct heap address of a java.lang.String instance, disabling garbage collection until ReleaseStringCritical is called.

http://www.ibm.com/support/knowledgecenter/SSYKE2_6.0.0/com.ibm.java.doc.diagnostics.60/diag/understanding/jni_copypin.html



好的,所以 IBM 基本上说 JNI 传递的对象可以随时移动! 怎么样热点 ?

GetArrayElements family of functions are documented to either copy arrays, or pin them in place (and, in so doing, prevent a compacting garbage collector from moving them). It is documented as a safer, less-restrictive alternative to GetPrimitiveArrayCritical. However, I'd like to know which VMs and/or garbage collectors (if any) actually pin arrays instead of copying them.

Which VMs or GCs support JNI pinning?



亚历山大似乎认为访问传递对象内存的唯一安全方法是通过 Get<PrimitiveType>ArrayElementsGetPrimitiveArrayCritical
特伦特的答案并不令人兴奋。

At least in current JVM's (i have not checked to see how far back this was backported), CMS GC, since it's non-moving is not affected by JNI critical sections (modulo that non stop-worl compaction can occur if there is a concurrent mode failure -- in that case the allocating thread must stall until the critical section is cleared -- this latter kind of stall is likely to be much rarer than the slow-path direct allocation in old gen pathology that you might see more frequently). Note that direct allocation in old gen is not only slow in and of itself (a first-order performance impact) but can in turn cause more tenuring (because of so-called nepotism), as well as slower subsequent scavenges because of more direty cards needing scanning (both of the latter being second-rder effects).

http://mail.openjdk.java.net/pipermail/hotspot-runtime-dev/2007-December/000074.html



OpenJDK 邮件列表上的这封电子邮件似乎说 ConcurrentMarkAndSweep GC 是不动的。

https://www.infoq.com/articles/G1-One-Garbage-Collector-To-Rule-Them-All



这篇关于 G1 的帖子提到它确实压缩了堆,但并没有特别提到移动数据。

由于 IBM 文档暗示可以随时压缩对象这一事实;我们需要弄清楚为什么 JNI HotSpot 函数实际上是安全的。是的,因为如果在 JNI 代码运行时确实发生了内存压缩,它们必须转移到安全状态以防止并发内存效应。

现在,我一直在尽我所能关注 HotSpot 代码。让我们看看GetByteArrayElements .在复制元素之前,该方法必须确保指针正确,这似乎是合乎逻辑的。让我们尝试找出方法。

这是 GetByteArrayElements 的宏
#ifndef USDT2
#define DEFINE_GETSCALARARRAYELEMENTS(ElementTag,ElementType,Result, Tag) 
JNI_QUICK_ENTRY(ElementType*,
          jni_Get##Result##ArrayElements(JNIEnv *env, ElementType##Array array, jboolean *isCopy))
  JNIWrapper("Get" XSTR(Result) "ArrayElements");
  DTRACE_PROBE3(hotspot_jni, Get##Result##ArrayElements__entry, env, array, isCopy);
  /* allocate an chunk of memory in c land */
  typeArrayOop a = typeArrayOop(JNIHandles::resolve_non_null(array));
  ElementType* result;
  int len = a->length();
  if (len == 0) {
    result = (ElementType*)get_bad_address();
  } else {
    result = NEW_C_HEAP_ARRAY_RETURN_NULL(ElementType, len, mtInternal);
    if (result != NULL) {                                    
          memcpy(result, a->Tag##_at_addr(0), sizeof(ElementType)*len);
      if (isCopy) {
        *isCopy = JNI_TRUE;
      }
    }  
  }
  DTRACE_PROBE1(hotspot_jni, Get##Result##ArrayElements__return, result);
  return result;
JNI_END

这是 JNI_QUICK_ENTRY 的宏
#define JNI_QUICK_ENTRY(result_type, header)                         \
extern "C" {                                                         \
  result_type JNICALL header {                                \
    JavaThread* thread=JavaThread::thread_from_jni_environment(env); \
    assert( !VerifyJNIEnvThread || (thread == Thread::current()), "JNIEnv is only valid in same thread"); \
    ThreadInVMfromNative __tiv(thread);                              \
    debug_only(VMNativeEntryWrapper __vew;)                          \
VM_QUICK_ENTRY_BASE(result_type, header, thread)

我已经关注了这里的每个功能,但还必须查看任何类型的互斥锁或内存同步器。我无法遵循的唯一功能是__tiv在我能找到的任何地方似乎都没有定义。
  • 有人可以向我解释为什么 JNI 接口(interface)方法如 GetByteArrayElements安全吗?
  • 当我们这样做时,任何人都可以找到 JNI 调用在 JNI_QUICK_ENTRY 时从 VM 转换回 Native 的位置吗?退出?
  • 最佳答案

    JNI 方法如何在 HotSpot JVM 中工作

  • native 方法可以与包括 GC 在内的 VM 操作同时运行。他们是 not stopped at safepoints .
  • GC 可能会移动 Java 对象,即使它们是从正在运行的 native 方法引用的。 jobject句柄只是对象引用的不可移动数组的索引。每当移动对象时,相应的数组槽就会更新,尽管索引保持不变。也就是说,jobject句柄仍然有效。
    每次本地方法调用 JNI 函数时,它都会检查 JVM 是否处于安全点状态。如果是(例如 GC 正在运行),JNI 功能会阻塞,直到安全点操作完成。
  • 在执行 JNI 函数期间,如 GetByteArrayElements ,对应的线程标记为 _thread_in_vm .在此状态下有正在运行的线程时无法到达安全点。例如。如果在执行 GetByteArrayElements 期间请求 GC , GC 会延迟到 JNI 函数返回。
  • 线程状态转换魔法是由您注意到的行执行的:ThreadInVMfromNative __tiv(thread) .这里__tiv只是类的一个实例。它的唯一目的是自动调用ThreadInVMfromNative构造函数和析构函数。

    ThreadInVMfromNative 构造函数调用 transition_from_native 其中checks用于安全点,并在需要时暂停当前线程。 ~ThreadInVMfromNative 析构函数切换回 _thread_in_native状态。
  • GetPrimitiveArrayCriticalGetStringCritical是唯一提供指向 Java 堆的原始指针的 JNI 函数。他们prevent GC from starting直到对应的Release函数被调用。

  • 从 native 代码调用 JNI 函数时的线程状态转换
  • 状态=_thread_in_native;
    native 方法可能与 GC 并发运行
  • JNI 函数调用
  • 状态 = _thread_in_native_trans;
    GC此时无法启动
  • 如果 VM 操作正在进行中,则阻塞直到它完成
  • 状态 = _thread_in_vm;
    安全访问堆
  • 关于java - 了解 JNI 参数的安全访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39381339/

    相关文章:

    使用 Xerces 的 getElementsByTagName 的 Java 代码不返回子节点

    c++ - 如何区分使用内存池分配的类

    c - C "for"循环出现问题 - 不会迭代过去的第一个

    c - 通过自定义比较进行高效排序,但没有回调函数

    java - 如何在显示JSP后执行hibernate session.close()以避免lazy=false

    java - 如何使用 Html.fromHtml 在 android 中显示心脏标志

    java - Fasterxml jackson : Remove double quotes

    c++ - 如何为商店数据库添加名称字段 (OOP/C++)

    c++ - 遍历 unordered_map C++

    c - 如何改变MPU9150的灵敏度?