了解JNI参数的安全访问 [英] Understanding safe access of JNI arguments

查看:223
本文介绍了了解JNI参数的安全访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm doing some research regarding how HotSpot performs garbage-collection and/or heap-compaction while JNI code is running.

似乎是常识可以随时在Java中移动对象。我试图明白,如果JNI受到垃圾收集效果的影响。存在许多JNI函数来明确地防止垃圾收集;例如 GetPrimitiveArrayCritical 。如果引用确实是volatile,那么这样的函数是有意义的。但是,如果不是这样的话就毫无意义。

It appears to be common knowledge that objects could be moved at any time in Java. I'm trying to understand, definitively if JNI is subject to effects garbage-collection. There exist a number of JNI functions to explicitly prevent garbage-collection; such as GetPrimitiveArrayCritical. It makes sense that such a function exists if the references are indeed volatile. However, it makes no sense if they are not.

关于这个问题,似乎存在大量相互矛盾的信息,我正试图将其解决。

There seems to be a substantial amount of conflicting information on this subject and I'm trying to sort it out.


JNI代码在安全点运行并且可以继续运行,除非它将
调回Java或调用某些特定的JVM方法,哪一点可以停止
以防止离开安全点(感谢Nitsan的
评论)。

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).

JVM用于在停止世界暂停期间阻止线程的机制

上面让我觉得垃圾收集将与JNI代码同时运行。那可能不安全,对吗?

The above makes me think that garbage-collection is going to run concurrently with JNI code. That can't be safe, right?


为了实现本地引用,Java VM为每个
转换创建一个注册表。从Java控制到本机方法。注册表将
不可移动的本地引用映射到Java对象,并保持对象
不被垃圾回收。传递给本机
方法的所有Java对象(包括那些作为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 引用是不可移动的,但是没有说明压缩。

Okay, so the local references are nonmovable but that doesn't say anything about the compaction.


JVM必须确保作为参数从Java™传递到
的本机方法,本机代码
创建的任何新对象仍可由GC访问。为了处理GC要求,JVM
分配了一个专门存储的小区域,称为本地
参考根集。

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:


  • 线程首先附加到JVM(线程的最外层根集)。

  • 每次发生J2N转换。

JVM使用以下命令初始化为J2N转换创建的根集:

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


  • 对调用者对象或类的本地引用。

  • 对每个传递的对象的本地引用作为本机方法的参数。

在本机代码中创建的新本地引用被添加到此J2N根
集中,除非你使用PushLocalFrame
JNI函数创建一个新的本地框架。

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 将传递的对象存储在中本地引用根集但它没有讨论内存压缩。这只是说对象不会被垃圾收集。

Okay, so IBM stores the passed objects in the local reference root set but it doesn't discuss about memory compaction. This just says that the objects won't be garbage-collected.


GC可能在任何时候决定它需要压缩
垃圾收集堆。压缩涉及将对象
从一个地址物理移动到另一个地址。这些对象可能由
JNI本地或全局引用引用。为了允许安全地进行压缩,
JNI引用不是指向堆的直接指针。至少一个级别
的间接隔离了本机代码与对象移动。

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.

如果本机方法需要获得内部
的直接可寻址性一个对象,情况比较复杂。
直接寻址或固定堆的要求是典型的,需要
来快速,共享访问大型原始数组。一个例子可能是
包括一个屏幕缓冲区。在这些情况下,JNI临界区可以使用
,这对程序员提出了额外的要求,因为这些函数的JNI描述中指定了
。有关详细信息,请参阅JNI
规范。

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返回Java™数组的直接堆地址,禁用垃圾回收直到调用相应的
    ReleasePrimitiveArrayCritical。

  • GetStringCritical返回java.lang.String实例的直接堆地址,在调用ReleaseStringCritical之前禁用垃圾收集。

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

好的,所以 IBM 基本上说JNI传递的对象可能是随时感动!怎么样 HotSpot

Okay, so IBM basically says that the JNI passed objects COULD be moved at any time! How about HotSpot?


GetArrayElements系列函数记录到
复制数组或引脚它们到位(并且,这样做,防止
压缩垃圾收集器移动它们)。它被记录为
更安全,更少限制的替代GetPrimitiveArrayCritical。
但是,我想知道哪些VM和/或垃圾收集器(如果有的话)
实际上是pin数组而不是复制它们。

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.

哪些虚拟机或GC支持JNI固定?

Aleksandr 似乎认为访问传递对象内存的唯一安全方法是通过 Get< PrimitiveType> ArrayElements GetPrimitiveArrayCritical

Aleksandr seems to think that the only safe way to access the memory of passed objects is through Get<PrimitiveType>ArrayElements or GetPrimitiveArrayCritical

Trent的答案是不过令人兴奋。

Trent's answer was less than exciting.


至少在当前的JVM中(我还没有检查过这个
被反向移植的距离), CMS GC,因为它不移动不受JNI
关键部分的影响(模数如果
存在并发模式失败,则可能发生非停止压缩 - 在这种情况下,分配
线程必须停止,直到临界区被清除 - 后者
类型的停摆可能比你可能更频繁地看到的旧病理学中的慢速直接
分配更为罕见。
请注意,旧版本中的直接分配不仅慢于
本身(一阶性能影响),反过来可以导致更多
的期限(因为所谓的裙带关系) ,以及后续的
清除速度较慢,因为需要扫描更多的直接牌(两个
后者都是二次效果)。

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).

< a href =http://mail.openjdk.java.net/pipermail/hotspot-runtime-dev/2007-December/000074.html =nofollow noreferrer> http://mail.openjdk.java.net /pipermail/hotspot-runtime-dev/2007-December/000074.html

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

This email on the OpenJDK mailing list seems to say that the ConcurrentMarkAndSweep GC is non-moving.


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

这篇文章关于G1 ment它确实压缩了堆,但没有特别关于移动数据。

This post about G1 mentions that it does compact the heap but not much specifically about moving data.

现在,我我一直在遵循HotSpot代码。让我们来看看 GetByteArrayElements 。在复制元素之前,该方法必须确保指针正确,这似乎是合乎逻辑的。让我们试着找出方法。

Now, I've been following the HotSpot code the best I can. Lets take a look at GetByteArrayElements. It seems logical that the method must ensure that the pointer is correct before copying the elements. Lets try to find out how.

这是 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 ,它似乎没有任何我能找到的定义。

I have followed every function in here and yet have to see any kind of mutex or memory synchronizer. The only function I could not follow was __tiv which does not seem to have a definition anywhere I could find.


  • 有人可以向我解释为什么JNI接口方法如 GetByteArrayElements 是安全的吗?

  • 当我们在这里时,当 JNI_QUICK_ENTRY 退出时,有人能找到JNI调用从VM转换回Native的位置吗?

  • Could someone explain to me why JNI interface methods such as GetByteArrayElements are safe?
  • While we're at it, can anyone find where the JNI call transitions from VM back to Native when JNI_QUICK_ENTRY exits?

推荐答案

JNI方法如何在HotSpot JVM中工作



How JNI methods work in HotSpot JVM


  1. 本机方法可以与包括GC在内的VM操作同时运行。它们是未在安全点停止

GC可能会移动Java对象,即使它们是从正在运行的本机方法引用的。 jobject handle只是一个不可移动的对象引用数组的索引。每当移动一个对象时,相应的数组槽都会更新,尽管索引保持不变。也就是说, jobject 句柄仍然有效。
每次本机方法调用JNI函数时,它都会检查JVM是否处于安全点状态。如果是(例如GC正在运行),JNI功能将一直阻塞,直到安全点操作完成。

GC may move Java objects even if they are referenced from a running native method. jobject handle is just an index into a non-movable array of object references. Whenever an object is moved, the corresponding array slot is updated, though the index remains the same. That is, jobject handle remains valid. Every time a native method calls a JNI function, it checks if JVM is in the safepoint state. If it is (e.g. GC is running), JNI function blocks until safepoint operation is completed.

在执行JNI函数期间,如 GetByteArrayElements ,相应的线程被标记为 _thread_in_vm 。在此状态下有正在运行的线程时,无法访问安全点。例如。如果在执行 GetByteArrayElements 期间请求GC,GC将被延迟直到JNI函数返回。

During the execution of JNI functions like GetByteArrayElements, the corresponding thread is marked as _thread_in_vm. A safepoint cannot be reached while there are running threads in this state. E.g. if GC is requested during the execution of GetByteArrayElements, GC will be delayed until JNI function returns.

线程状态转换魔术由您注意到的行执行:

ThreadInVMfromNative __tiv(thread)。这里 __ tiv 只是该类的一个实例。它的唯一目的是自动调用 ThreadInVMfromNative 构造函数和析构函数。

Thread state transition magic is performed by the line you've noticed:
ThreadInVMfromNative __tiv(thread). Here __tiv is just an instance of the class. Its only purpose is to automatically call ThreadInVMfromNative constructor and destructor.

ThreadInVMfromNative 构造函数调用 transition_from_native 检查安全点,并在需要时暂停当前线程。 ~ThreadInVMfromNative 析构函数切换回 _thread_in_native 州。

ThreadInVMfromNative constructor calls transition_from_native which checks for a safepoint, and suspends current thread if needed. ~ThreadInVMfromNative destructor switches back to _thread_in_native state.

GetPrimitiveArrayCritical GetStringCritical 是唯一提供Java堆原始指针的JNI函数。他们预防GC从开始到相应的 Release 函数被调用。

GetPrimitiveArrayCritical and GetStringCritical are the only JNI functions that provide raw pointers to Java heap. They prevent GC from starting until the corresponding Release function is called.



从本机代码调用JNI函数时的线程状态转换



Thread state transition when calling a JNI function from native code


  1. state = _thread_in_native;

    本机方法可以与GC同时运行

  1. state = _thread_in_native;
    Native method may run concurrently with GC

调用JNI函数

state = _thread_in_native_trans;

GC此时无法启动

state = _thread_in_native_trans;
GC cannot start at this point

如果是VM操作正在进行,阻止直至完成

If VM operation is in progress, block until it completes

state = _thread_in_vm;

安全访问堆

state = _thread_in_vm;
Safe to access heap

这篇关于了解JNI参数的安全访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆