欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

System.gc整理

程序员文章站 2022-04-18 19:23:36
...

System.gc整理

  • System.gc()

    • 源码
      public static void gc() { Runtime.getRuntime().gc(); }
    • api解释(看仔细了,一定执行gc吗?抛开一些控制参数不说,尽最大努力)
      Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects.
  • Runtime.gc()

    • 源码
      public native void gc();
  • Runtime.c Java_java_lang_Runtime_gc

    • 源码
      JNIEXPORT void JNICALL Java_java_lang_Runtime_gc(JNIEnv *env, jobject this) { JVM_GC(); }
  • jvm.cpp

    • 源码
      JVM_ENTRY_NO_ENV(void, JVM_GC(void)) JVMWrapper("JVM_GC"); if (!DisableExplicitGC) {//heap()返回当前的heap,要看当前是什么heap Universe::heap()->collect(GCCause::_java_lang_system_gc); } JVM_END
  • Universe.cpp

    • 源码
      jint universe_init() { ... jint status = Universe::initialize_heap(); if (status != JNI_OK) { return status; } ... return JNI_OK; }
    • CollectedHeap* Universe::_collectedHeap = NULL;
      • initialize_heap()
        jint Universe::initialize_heap() { if (UseParallelGC) { #if INCLUDE_ALL_GCS Universe::_collectedHeap = new ParallelScavengeHeap(); #else // INCLUDE_ALL_GCS fatal("UseParallelGC not supported in this VM."); #endif // INCLUDE_ALL_GCS } else if (UseG1GC) { #if INCLUDE_ALL_GCS G1CollectorPolicyExt* g1p = new G1CollectorPolicyExt(); g1p->initialize_all(); G1CollectedHeap* g1h = new G1CollectedHeap(g1p); Universe::_collectedHeap = g1h; #else // INCLUDE_ALL_GCS fatal("UseG1GC not supported in java kernel vm."); #endif // INCLUDE_ALL_GCS } else {//这里 GenCollectorPolicy *gc_policy; if (UseSerialGC) { gc_policy = new MarkSweepPolicy(); } else if (UseConcMarkSweepGC) {//这里 #if INCLUDE_ALL_GCS if (UseAdaptiveSizePolicy) { gc_policy = new ASConcurrentMarkSweepPolicy(); } else {//这里****** gc_policy = new ConcurrentMarkSweepPolicy(); } #else // INCLUDE_ALL_GCS fatal("UseConcMarkSweepGC not supported in this VM."); #endif // INCLUDE_ALL_GCS } else { // default old generation gc_policy = new MarkSweepPolicy(); } gc_policy->initialize_all();***** Universe::_collectedHeap = new GenCollectedHeap(gc_policy);***** } ThreadLocalAllocBuffer::set_max_size(Universe::heap()->max_tlab_size()); jint status = Universe::heap()->initialize(); if (status != JNI_OK) { return status; } ... // We will never reach the CATCH below since Exceptions::_throw will cause // the VM to exit if an exception is thrown during initialization if (UseTLAB) { assert(Universe::heap()->supports_tlab_allocation(), "Should support thread-local allocation buffers"); ThreadLocalAllocBuffer::startup_initialization(); } return JNI_OK; }
  • genCollectedHeap.cpp中collect()方法实现

    • 源码
      void GenCollectedHeap::collect(GCCause::Cause cause) { if (should_do_concurrent_full_gc(cause)) { #if INCLUDE_ALL_GCS // mostly concurrent full collection collect_mostly_concurrent(cause);//并发 #else // INCLUDE_ALL_GCS ShouldNotReachHere(); #endif // INCLUDE_ALL_GCS } else if (cause == GCCause::_wb_young_gc) { // minor collection for WhiteBox API collect(cause, 0); } else {// STOP THE WORLD #ifdef ASSERT if (cause == GCCause::_scavenge_alot) { // minor collection only collect(cause, 0); } else { // Stop-the-world full collection collect(cause, n_gens() - 1); } #else // Stop-the-world full collection collect(cause, n_gens() - 1); #endif } }
    • should_do_concurrent_full_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)); }
  • 文字说明

    • 1.System.gc()在CMS情况下,DisableExplicitGC=false,打开ExplicitGCInvokesConcurrent情况下,走并发full-gc
    • 否则走stop-the-world full-gc
    • 2.JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,
      • 比如通过jvmti做强制GC,通过System.gc触发,还可以通过jmap来触发等
    • 3.System.gc常识
      • system.gc其实是做一次full gc
      • system.gc会暂停整个进程
      • system.gc一般情况下我们要禁掉,使用-XX:+DisableExplicitGC
      • system.gc在cms gc下我们通过-XX:+ExplicitGCInvokesConcurrent来做一次稍微高效点的GC(效果比Full GC要好些)
      • system.gc最常见的场景是RMI/NIO下的堆外内存分配等
    • 4.native方法查找
      • 1.jdk里一般含有native方法的类,一般都会有一个对应的c文件,比如上面的java.lang.Runtime这个类,会有一个Runtime.c的文件和它对应,native方法的具体实现都在里面了
        • 方法名可能也猜到规则了,Java_pkgName_className_methodName,其中pkgName里的".“替换成”_“,这样就能找到了,当然规则不仅仅只有这么一个,还有其他的,这里不细说了
      • 2.unsafe.cpp这个就是.cpp文件而不是.c文件
    • 5.查看一个jvm参数默认值
      • java -XX:+PrintFlagsFinal -version | grep DisableExplicitGC
    • 6.并行Full GC相对正常的Full GC效率高在哪里
      • vmThread

        • A single VMThread (the primordial(原始的) thread) spawns all other threads
        • and is itself used by other threads to offload(卸下) heavy vm operations
        • like scavenge, garbage_collect etc.
        • VMOperationQueue
        • VM_Operation
        • 是jvm里面的线程母体,根据hotspot源码(vmThread.hpp)里面的注释,它是一个单例的对象(最原始的线程)会产生或触发所有其他的线程,这个单个的VM线程是会被其他线程所使用来做一些VM操作(如,清扫垃圾等)。
        • 在 VMThread 的结构体里有一个VMOperationQueue列队,所有的VM线程操作(vm_operation)都会被保存到这个列队当中,VMThread 本身就是一个线程,它的线程负责执行一个自轮询的loop函数(具体可以参考:
        • VMThread.cpp里面的void VMThread::loop()) ,该loop函数从VMOperationQueue列队中按照优先级取出当前需要执行的操作对象(VM_Operation),并且调用VM_Operation->evaluate函数去执行该操作类型本身的业务逻辑。
        • ps:VM操作类型被定义在vm_operations.hpp文件内,列举几个:ThreadStop、ThreadDump、PrintThreads、GenCollectFull、GenCollectFullConcurrent、CMS_Initial_Mark、CMS_Final_Remark…..
      • stop the world gc

        • 说到GC,这里要先提到VMThread,在jvm里有这么一个线程不断轮询它的队列,这个队列里主要是存一些VM_operation的动作,
        • 比如最常见的就是内存分配失败要求做GC操作的请求等,在对gc这些操作执行的时候会先将其他业务线程都进入到安全点
        • 也就是这些线程从此不再执行任何字节码指令,只有当出了安全点的时候才让他们继续执行原来的指令
        • 因此这其实就是我们说的stop the world(STW),整个进程相当于静止了
      • CMS GC

        这里必须提到CMS GC,因为这是解释并行Full GC和正常Full GC的关键所在,CMS GC我们分为两种模式background和foreground,
        其中background顾名思义是在后台做的,也就是可以不影响正常的业务线程跑,触发条件比如说old的内存占比超过多少的时候就可能
        触发一次background式的cms gc,这个过程会经历CMS GC的所有阶段,该暂停的暂停,该并行的并行,效率相对来说还比较高,
        毕竟有和业务线程并行的gc阶段;而foreground则不然,它发生的场景比如业务线程请求分配内存,但是内存不够了
        于是可能触发一次cms gc,
        这个过程就必须是要等内存分配到了线程才能继续往下面走的,因此整个过程必须是STW的,因此CMS GC整个过程都是暂停应用的,
        但是为了提高效率,它并不是每个阶段都会走的,只走其中一些阶段,这些省下来的阶段主要是并行阶段,Precleaning、AbortablePreclean,
        Resizing这几个阶段都不会经历,其中sweep阶段是同步的,但不管怎么说如果走了类似foreground的cms gc,
        那么整个过程业务线程都是不可用的,效率会影响挺大。CMS GC具体的过程后面再写文章详细说,其过程确实非常复杂的
        - 正常的Full GC
        - 正常的Full GC其实是整个gc过程包括ygc和cms gc(这里说的是真正意义上的Full GC,还有些场景虽然调用Full GC的接口,
        - 但是并不会都做,有些时候只做ygc,有些时候只做cms gc)都是由VMThread来执行的,因此整个时间是ygc+cms gc的时间之和,
        - 其中CMS GC是上面提到的foreground式的,因此整个过程会比较长,也是我们要避免的
        - 并行的Full GC
        - 并行Full GC也通样会做YGC和CMS GC,但是效率高就搞在CMS GC是走的background的,
        - 整个暂停的过程主要是YGC+CMS_initMark+CMS_remark几个阶段
        - 堆外内存常配合使用System GC
        - 这里说的堆外内存主要针对java.nio.DirectByteBuffer,这些对象的创建过程会通过Unsafe接口直接通过os::malloc来分配内存,
        - 然后将内存的起始地址和大小存到java.nio.DirectByteBuffer对象里,这样就可以直接操作这些内存。
        - 这些内存只有在DirectByteBuffer回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,
        - 但是一直没有触发CMS GC或者Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,
        - 通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,
        - 以此来回收掉没有被使用的堆外内存
        - 7.CMS
        - cms gc 状态
        - 当触发 cms gc 对老年代进行垃圾收集时,算法中会使用_collectorState变量记录执行状态,整个周期分成以下几个状态:
        - Idling:一次 cms gc 生命周期的初始化状态。
        - InitialMarking:根据 gc roots,标记出直接可达的活跃对象,这个过程需要stw的。
        - Marking:根据 InitialMarking 阶段标记出的活跃对象,并发迭代遍历所有的活跃对象,这个过程可以和用户线程并发执行。
        - Precleaning:并发预清理。
        - AbortablePreclean:因为某些原因终止预清理。
        - FinalMarking:由于marking阶段是和用户线程并发执行的,该过程中可能有用户线程修改某些活跃对象的字段,指向了一个非标记过的对象,
        - 在这个阶段需要重新标记出这些遗漏的对象,防止在下一阶段被清理掉,这个过程也是需要stw的。
        - Sweeping:并发清理掉未标记的对象。
        - Resizing:如果有需要,重新调整堆大小。稳定堆和非稳定堆
        - Resetting:重置数据,为下一次的 cms gc 做准备。
        - cms gc 和 full gc 的区别
        - cms gc 通过一个后台线程触发,触发机制是默认每隔2秒判断一下当前老年代的内存使用率是否达到阈值,当然具体的触发条件没有这么简单,
        - 如果是则触发一次cms gc,在该过程中只会标记出存活对象,然后清除死亡对象,期间会产生碎片空间
        - full gc 是通过 vm thread 执行的,整个过程是 stop-the-world,在该过程中会判断当前 gc 是否需要进行compact,
        - 即把存活对象移动到内存的一端,可以有效的消除cms gc产生的碎片空间。
        - cms gc 如何触发(可以看源码实现)
        - 最晚每 2 秒(-XX:CMSWaitDuration)进行一次判断
        - 1、如果_full_gc_requested为真,说明有明确的需求要进行gc,比如调用System.gc();
        - 2、CMS 默认采用 jvm 运行时的统计数据判断是否需要触发 cms gc,如果需要根据 CMSInitiatingOccupancyFraction 的值
        - 进行判断,需要设置参数-XX:+UseCMSInitiatingOccupancyOnly
        - 3、如果开启了UseCMSInitiatingOccupancyOnly参数,判断当前老年代使用率是否大于阈值,则触发 cms gc,
        - 该阈值可以通过参数-XX:CMSInitiatingOccupancyFraction进行设置,如果没有设置,默认为92%;
        - 4、如果之前的 ygc 失败过,或则下次新生代执行 ygc 可能失败,这两种情况下都需要触发 cms gc;
        - 5、CMS 默认不会对永久代进行垃圾收集,如果希望对永久代进行垃圾收集,需要设置参数-XX:+CMSClassUnloadingEnabled,
        - 如果开启了CMSClassUnloadingEnabled,根据永久带的内存使用率判断是否触发 cms gc;
        - 6、...还有一些其它情况
        - 如果有上述几种情况,说明需要执行一次 cms gc,通过调用_collector->collect_in_background(false, cause) 进行触发,
        - 注意这个方法名中的in_background
        - full gc 如何触发
        - 触发 full gc 的主要原因是在eden区为对象或TLAB分配内存失败,导致一次 ygc
        - 在 GenCollectorPolicy 类的satisfy_failed_allocation()方法中有这么一段逻辑:
        if (!gch->incremental_collection_will_fail(false /* don't consult_young */)) { // Do an incremental collection. gch->do_collection(false /* full */, false /* clear_all_soft_refs */, size /* size */, is_tlab /* is_tlab */, number_of_generations() - 1 /* max_level */); } else { if (Verbose && PrintGCDetails) { gclog_or_tty->print(" :: Trying full because partial may fail :: "); } // Try a full collection; see delta for bug id 6266275 // for the original code and why this has been simplified // with from-space allocation criteria modified and // such allocation moved out of the safepoint path. gch->do_collection(true /* full */, false /* clear_all_soft_refs */, size /* size */, is_tlab /* is_tlab */, number_of_generations() - 1 /* max_level */); }
        - 该方法是由 vm thread 执行的,整个过程都是 stop-the-world,如果当前incremental_collection_will_fail方法返回 false,
        - 则会放弃本次的 ygc,直接触发一次 full gc,incremental_collection_will_fail实现如下:
        bool incremental_collection_will_fail(bool consult_young) { // Assumes a 2-generation system; the first disjunct remembers if an // incremental collection failed, even when we thought (second disjunct) // that it would not. assert(heap()->collector_policy()->is_two_generation_policy(), "the following definition may not be suitable for an n(>2)-generation system"); return incremental_collection_failed() || (consult_young && !get_gen(0)->collection_attempt_is_safe()); }
        - 其中参数 consult_young 为 false,如果incremental_collection_failed()返回 true,
        - 会导致执行很慢很慢很慢的full gc,
        - 如果上一次 ygc 过程中发生 promotion failure 时
        - 会设置 _incremental_collection_failed为 true,即方法incremental_collection_failed()返回 true,
        - 相当于触发了 full gc
        - 还有一种情况是,当发生ygc之后,还是没有足够的内存进行分配,这时会继续触发 full gc
        `// If we reach this point, we're really out of memory. Try every trick
        // we can to reclaim memory. Force collection of soft references. Force
        // a complete compaction of the heap. Any additional methods for finding
        // free memory should be here, especially if they are expensive. If this
        // attempt fails, an OOM exception will be thrown.
        {
        IntFlagSetting flag_change(MarkSweepAlwaysCompactCount, 1); // Make sure the heap is fully compacted

              gch->do_collection(true             /* full */,
                              true             /* clear_all_soft_refs */,
                              size             /* size */,
                              is_tlab          /* is_tlab */,
                              number_of_generations() - 1 /* max_level */);
          }`
        
      • concurrent model failure?

        • 在CMS中,full gc 也叫 The foreground collector,对应的 cms gc 叫 The background collector,
        • 在真正执行 full gc 之前会判断一下 cms gc 的执行状态,如果 cms gc 正处于执行状态
        • 调用report_concurrent_mode_interruption()方法,通知事件 concurrent mode failure
          CollectorState first_state = _collectorState; if (first_state > Idling) {//这里是大于,哈哈 report_concurrent_mode_interruption(); } // void CMSCollector::report_concurrent_mode_interruption() { if (is_external_interruption()) { if (PrintGCDetails) { gclog_or_tty->print(" (concurrent mode interrupted)"); } } else { if (PrintGCDetails) { gclog_or_tty->print(" (concurrent mode failure)"); } _gc_tracer_cm->report_concurrent_mode_failure(); } }
        • 这里可以发现是 full gc 导致了concurrent mode failure,而不是因为concurrent mode failure 错误导致触发 full gc,
        • 真正触发 full gc 的原因可能是 ygc 时发生的promotion failure。
        • 其实这里还有concurrent mode interrupted,这是由于外部因素触发了 full gc,比如执行了System.gc(),导致了这个原因。
      • full gc中的compact

        • 每次触发 full gc,会根据should_compact 标识进行判断是否需要执行 compact ,判断实现如下:
          *should_compact = UseCMSCompactAtFullCollection && ((_full_gcs_since_conc_gc >= CMSFullGCsBeforeCompaction) || GCCause::is_user_requested_gc(gch->gc_cause()) || gch->incremental_collection_will_fail(true /* consult_young */));
        • UseCMSCompactAtFullCollection默认开启,但是否要进行 compact,还得看后面的条件:
        • 1、最近一次cms gc 以来发生 full gc 的次数_full_gcs_since_conc_gc(这个值每次执行完 cms gc 的sweeping
          • 阶段就会设置为0)达到阈值CMSFullGCsBeforeCompaction 。(但是阈值默认为0,哪里有设置它的地方,
          • 不会每次 full gc 都是compact吧?)
        • 2、用户强制执行了gc,如System.gc()。
        • 3、上一次 ygc 已经失败(发生了promotion failure),或预测下一次 ygc 不会成功。
        • 如果上述条件都不满足,是否就一直不进行 compact,这样碎片问题就得不到缓解了,幸好还有补救的机会,实现如下:

          普通的 full gc,参数clear_all_soft_refs为 false,不会清理软引用,如果在执行完 full gc,空间还是不足的话,
          会执行一次彻底的 full gc,尝试清理所有的软引用,想方设法的收集可用内存,这种情况clear_all_soft_refs为 true,
          而且CMSCompactWhenClearAllSoftRefs默认为 true,在垃圾收集完可以执行一次compact,如果真的走到了这一步,
          该好好的查查代码了,因为这次 gc 的暂停时间已经很长很长很长了。

  • References