简书 占小狼
转载请注明原创出处,谢谢!
由于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动作。
由于放弃了本次的YGC,如果不是TLAB,会尝试在老年代进行分配,并返回,所以一次JNI操作可以会引起正常的对象直接分配在老年代的空间,这时GC_locker::is_active_and_needs_gc()
已经成立了,会设置一个gc locker,即_gc_locked
赋值为true。
到这里,整个YGC就结束了,当然本次YGC什么都没有回收,GC日志也什么都不会打印,只是设置了一些关键标识,_gc_locked
和_need_gc
之类的,供后续逻辑使用。
这时线程B也来申请内存,(线程C还在临界区),自然eden区的空间是不足的,当然不能茹莽的直接去触发一次YGC,JVM也没这么蠢,首先会进行如下判断,看看是不是可以直接在老年代进行分配:
这里有一个判断逻辑should_try_older_generation_allocation
,实现如下:
这里有一个条件是我们目前需要关心的,GC_locker::is_active_and_needs_gc()
,从上面的分析来看,这个条件肯定是成立的,所以结果是可以在老年代进行内存分配。
内存分配attempt_allocation(size, is_tlab, first_only)
的实现:
很显然,目前参数first_only
为false,在新生代的allocate动作不可能申请成功,接着会尝试在老年代申请。
如果万一,老年代也没有足够的内存,一般情况下不会发生,不过极端情况我们也是需要考虑的,真的发生的话,还会尝试进行扩容再分配。
如果已经不能扩容了,则执行下面的逻辑:
很显然,线程B当前并没有在临界区,则执行GC_locker::stall_until_clear()
,接着看这个方法的实现:
这里用到了之前设置的_need_gc
参数,线程B就在JNICritical_lock锁上等待。
如果添加了-XX+PrintJNIGCStalls
参数,那么由于JNI引起的一些奇奇怪怪GC问题就好查多了。