我正在研究 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.
好的,所以 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.
好的,所以 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.
亚历山大似乎认为访问传递对象内存的唯一安全方法是通过
Get<PrimitiveType>ArrayElements
或 GetPrimitiveArrayCritical
特伦特的答案并不令人兴奋。
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
在我能找到的任何地方似乎都没有定义。GetByteArrayElements
安全吗? JNI_QUICK_ENTRY
时从 VM 转换回 Native 的位置吗?退出? 最佳答案
JNI 方法如何在 HotSpot JVM 中工作
jobject
句柄只是对象引用的不可移动数组的索引。每当移动对象时,相应的数组槽就会更新,尽管索引保持不变。也就是说,jobject
句柄仍然有效。每次本地方法调用 JNI 函数时,它都会检查 JVM 是否处于安全点状态。如果是(例如 GC 正在运行),JNI 功能会阻塞,直到安全点操作完成。
GetByteArrayElements
,对应的线程标记为 _thread_in_vm
.在此状态下有正在运行的线程时无法到达安全点。例如。如果在执行 GetByteArrayElements
期间请求 GC , GC 会延迟到 JNI 函数返回。 ThreadInVMfromNative __tiv(thread)
.这里__tiv
只是类的一个实例。它的唯一目的是自动调用ThreadInVMfromNative
构造函数和析构函数。ThreadInVMfromNative
构造函数调用 transition_from_native
其中checks用于安全点,并在需要时暂停当前线程。 ~ThreadInVMfromNative
析构函数切换回 _thread_in_native
状态。 GetPrimitiveArrayCritical
和 GetStringCritical
是唯一提供指向 Java 堆的原始指针的 JNI 函数。他们prevent GC from starting直到对应的Release
函数被调用。 从 native 代码调用 JNI 函数时的线程状态转换
native 方法可能与 GC 并发运行
GC此时无法启动
安全访问堆
关于java - 了解 JNI 参数的安全访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39381339/