Osheep

时光不回头,当下最重要。

JVM源码分析之由JNI操作引起的迷惑性GC

简书 占小狼
转载请注明原创出处,谢谢!

由于JNI操作中的GC locker,会导致一次正常的YGC变得扑朔迷离,还不清楚GC locker的可以看看传送门 – JVM源码分析之GC locker深度分析

下面使用3个线程来讲解在eden区申请内存不够时的各种情况
1、线程A和B往eden区塞对象
2、线程C负责执行JNI方法

假设之前一次GC都没有发生过,线程A在eden区分配出现空间不足,这时会触发一次Allocation Failed(VM_GenCollectForAllocation)的GC,但这次GC最终能不能执行取决于GC locker的状态。

如果这个时候,线程C正在临界区(critical region)里面,那由线程A分配失败引起的YGC的就会被丢弃,设置_need_gc标识,标记线程C在离开临界区的时候需要触发一次GC动作。

《JVM源码分析之由JNI操作引起的迷惑性GC》

由于放弃了本次的YGC,接下去还会尝试进行扩容,如果可以的话,在扩容之后再申请内存,但是如果设置-xmx-xms相等的话,就无法进行扩容分配了,返回的_res就是一个NULL,这时GC_locker::is_active_and_needs_gc()已经成立了,会设置一个gc locker,即_gc_locked赋值为true。

到这里,整个YGC就结束了,当然本次YGC什么都没有回收,GC日志也什么都不会打印,只是设置了一些关键标识,_gc_locked_need_gc之类的,供后续逻辑使用。

这时线程B也来申请内存,(线程C还在临界区),自然eden区的空间是不足的,当然不能茹莽的直接去触发一次YGC,JVM也没这么蠢,首先会进行如下判断,看看是不是可以直接在老年代进行分配:

《JVM源码分析之由JNI操作引起的迷惑性GC》

这里有一个判断逻辑should_try_older_generation_allocation,实现如下:

《JVM源码分析之由JNI操作引起的迷惑性GC》

这里有一个条件是我们目前需要关心的,GC_locker::is_active_and_needs_gc(),从上面的分析来看,这个条件肯定是成立的,所以结果是可以在老年代进行内存分配。

内存分配attempt_allocation(size, is_tlab, first_only)的实现:

《JVM源码分析之由JNI操作引起的迷惑性GC》

很显然,目前参数first_only为false,在新生代的allocate动作不可能申请成功,接着会尝试在老年代申请。

如果万一,老年代也没有足够的内存,一般情况下不会发生,不过极端情况我们也是需要考虑的,真的发生的话,还会尝试进行扩容再分配。

如果已经不能扩容了,则执行下面的逻辑:

《JVM源码分析之由JNI操作引起的迷惑性GC》

很显然,线程B当前并没有在临界区,则执行GC_locker::stall_until_clear(),接着看这个方法的实现:

《JVM源码分析之由JNI操作引起的迷惑性GC》

这里用到了之前设置的_need_gc参数,线程B就在JNICritical_lock锁上等待。

如果添加了-XX+PrintJNIGCStalls参数,那么由于JNI引起的一些奇奇怪怪GC问题就好查多了。

点赞