优质广告供应商

广告是为了更好地支持作者创作

JVM源码分析之GC locker深度分析

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

GC locker是什么?

概念太抽象,一两句话说不清...

先看看它被使用的场景,jni.cpp类中有一对函数,分别是jni_GetStringCriticaljni_ReleaseStringCritical,看名字也知道一个Get,一个Release,有点像锁的感觉。

当使用本地方法JNI函数访问JVM中的字符串或数组数据时,就需要用到上面的两个函数,如下是JNI读取JVM中的字符串实现:

实现步骤:
1、从JVM返回字符串指针
2、通过MALLOC_MIN4分配内存
3、如果内存不足,抛出OOM异常
4、拷贝字符串的值
5、执行ReleaseStringCritical

这里GetStringCriticalReleaseStringCritical有何用?

这两个函数之间的代码必须在"critical region"(临界区)执行,即在调用GetStringCritical函数开始,到数据复制完成期间,必须保证原始数据不被修改,那应该如何防止其它线程的操作、或发生GC回收改字符串对象?

GetStringCritical函数实现:

可以发现,在返回字符串指针之前执行了GC_locker::lock_critical(thread);对临界区进行了加锁。

GC_locker::lock_critical(thread)方法实现:

每个线程都有一个参数_jni_active_critical,记录当前进入的临界区个数,如果_jni_active_critical > 0,说明该线程已经在临界区。

如果该线程还没进入临界区,且_need_gc标识为true,则执行jni_lock方法。_need_gc默认为false,只有在特殊情况下才会被设置为true。

如果现在有线程在临界区中,且_need_gc标识为true,则通过JNICritical_lock锁阻塞线程等待,实现如下:

假设现在线程A执行了enter_critical(),第一次进入临界区,这时不争气的JVM发生了YGC,而临界区的代码还没有执行完,怎么办?

我们先来看一段代码,触发年轻代的垃圾回收之前,会进行如下判断:

if (GC_locker::check_active_before_gc()) {
    return; // GC is disabled (e.g. JNI GetXXXCritical operation)
}

check_active_before_gc()的实现:

如果发现有线程进入了临界区,本次的GC需要被disabled(丢弃),并设置_need_gc为true,这正好和前面的对应起来。

技巧:通过添加-XX+PrintJNIGCStalls可以打印出进入临界区的线程信息。

因为有线程进入了临界区,导致本次触发的YGC被丢弃了,那内存分配时空间不足的问题怎么解决?

线程执行完临界区的代码之后,执行ReleaseStringCritical方法,离开临界区:

GC_locker::unlock_critical的方法实现:

如果这个临界区是该线程最后一个临界区,且_need_gc为true,就是上次YGC丢弃时设置的true,则执行jni_unlock方法,实现如下:

实现有点复杂,总之就是把进入临界区时对一些字段进行+1操作,现在执行-1操作,如果发现已经没有任何线程在临界区了,说明该线程是最后一个离开临界区的线程。

如果当前没有其它地方持有CG locker的lock锁,则触发一次原因为GCCause::_gc_locker的GC,最后通过JNICritical_lock->notify_all()唤醒被阻塞在临界区外面的线程。

collect(GCCause::Cause cause)的方法实现:

先通过should_do_concurrent_full_gc判断是否可以使用并发GC,判断逻辑如下:

bool GenCollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) {
  return UseConcMarkSweepGC &&
         ((cause == GCCause::_gc_locker && GCLockerInvokesConcurrent) ||
          (cause == GCCause::_java_lang_system_gc && ExplicitGCInvokesConcurrent));
}

前提是使用CMS垃圾算法
1、GCCause::_gc_locker 对应 -XX+GCLockerInvokesConcurrent
2、GCCause::_java_lang_system_gc 对应 -XX+ExplicitGCInvokesConcurrent

如果开启了-XX+GCLockerInvokesConcurrent,并发的实现如下:

VM_GenCollectFullConcurrent::doit()实现,这是GC开始的入口:

大吃一惊,居然有3情况,然后傻傻分不清楚:
1、只收集年轻代,如果你真的以为只收集年轻代,那就太年轻了,实现如下:

注意:如果_incremental_collection_failed标识为false,那么会执行一次真正意义的full gc,真的很惨!

2、触发一次CMS,如何触发?

设置_full_gc_requested参数为true,在周期性触发CMS的条件中,包含了对该标识的判断,如果为true,则会执行CMS

3、啥都不做

如果没有开启-XX+GCLockerInvokesConcurrent,则执行collect(cause, n_gens() - 1)

VM_GenCollectFull::doit()实现如下:

do_full_collection的逻辑和并发情况下只收集年轻代的逻辑差不多,正常情况下,也只会对年轻代收集,如果incremental_collection_will_fail返回true的话,就对年轻代和老年代都收集了。

这么分析下来,GC locker导致的GC问题,真是飘忽不定,头有点晕...我前面都在说什么?

优质广告供应商

广告是为了更好地支持作者创作

推荐阅读更多精彩内容

  • 简书 占小狼转载请注明原创出处,谢谢! 由于JNI操作中的GC locker,会导致一次正常的YGC变得扑朔迷离,...
    占小狼阅读 2,425评论 1 7
  • JNI & gclocker 整理 jni specificationhttp://docs.oracle.com...
    andersonoy阅读 1,551评论 0 0
  • 优质广告供应商

    广告是为了更好地支持作者创作

  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,295评论 0 11
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 41,063评论 11 349
  • 简书 占小狼转载请注明原创出处,谢谢! 数组动态扩容导致频繁FGC 关于数组动态扩容导致频繁GC的问题,笨神又写了...
    占小狼阅读 6,155评论 17 46
  • 3月31日上午,一则“贵州省印江自治县某学校多名女生殴打另一名女生”的视频在微博上引起舆论关注。 一、热点回顾 视...
    郭晓光阅读 821评论 5 3
  • 优质广告供应商

    广告是为了更好地支持作者创作

  • 今天完成了作业PPT和流程图。根据邓老师的要求对PPT重新进行了修改。 放学的时候李老师让我运行了程序,录入分数的...
    王子龙同学阅读 456评论 0 49
  • 在创意工作中,80%的注意力应该放在自我内心上,而不是作品上,不需要想象自己的作品做出来之后会成为什么样子,会有什...
    wangrad阅读 319评论 0 0