System.gc() 源码解读

作者: 涤生YQ | 来源:发表于2018-01-14 18:39 被阅读367次

    System.gc() 源码解读

    简书 涤生
    转载请注明原创出处,谢谢!
    如果读完觉得有收获的话,欢迎点赞加关注。

    介绍

    System.gc(),大家应该也有所了解,是JDK提供的触发Full GC的一种方式,会触发Full GC,其间会stop the world,对业务影响较大,一般情况下不会直接使用。
    那它是如何实现的呢?
    另外有哪些参数可以进行优化呢?
    我们带着问题来对相关源码解读一下。

    实现

    JDK实现

    /**
         * Runs the garbage collector.
         * <p>
         * Calling the <code>gc</code> 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.
         * <p>
         * The call <code>System.gc()</code> is effectively equivalent to the
         * call:
         * <blockquote><pre>
         * Runtime.getRuntime().gc()
         * </pre></blockquote>
         *
         * @see     java.lang.Runtime#gc()
         */
        public static void gc() {
            Runtime.getRuntime().gc();
        }
    

    其实就是调用了Runtime类的gc方法。

    /**
         * Runs the garbage collector.
         * Calling this 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 virtual machine has made
         * its best effort to recycle all discarded objects.
         * <p>
         * The name <code>gc</code> stands for "garbage
         * collector". The virtual machine performs this recycling
         * process automatically as needed, in a separate thread, even if the
         * <code>gc</code> method is not invoked explicitly.
         * <p>
         * The method {@link System#gc()} is the conventional and convenient
         * means of invoking this method.
         */
        public native void gc();
    

    Runtime类的gc方法是个native方法,所以只能进入JVM代码去看其真正的实现了。

    JVM实现

    打开openjdk的源码,Runtime类的gc方法在Runtime.c文件中有具体的实现

    JNIEXPORT void JNICALL
    Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
    {
        JVM_GC();
    }
    

    可以看到直接调用了JVM_GC()方法,这个方法的实现在jvm.cpp中

    JVM_ENTRY_NO_ENV(void, JVM_GC(void))
      JVMWrapper("JVM_GC");
      if (!DisableExplicitGC) {
        Universe::heap()->collect(GCCause::_java_lang_system_gc);
      }
    JVM_END
    

    很明显最终调用的是heap的collect方法,gcCause为_java_lang_system_gc。这里有个注意点就是DisableExplicitGC,如果是true就不会执行collect方法,也就是使得System.gc()无效,DisableExplicitGC这个参数对应到的配置就是-XX:+DisableExplicitGC,默认是false,如果配置了,就是true。

    heap有几种,具体是哪种heap,需要看gc算法,如常用的CMS GC的对应的heap是GenCollectedHeap,所以我们再看看GenCollectedHeap.cpp对应的collect方法

    void GenCollectedHeap::collect(GCCause::Cause cause) {
      if (should_do_concurrent_full_gc(cause)) {
    #ifndef SERIALGC
        // mostly concurrent full collection
        collect_mostly_concurrent(cause);
    #else  // SERIALGC
        ShouldNotReachHere();
    #endif // SERIALGC
      } else {
    #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方法判断是不是进行一次并发Full GC,如果是则调用collect_mostly_concurrent方法,进行并发Full GC;如果不是则一般会走到collect(cause, n_gens() - 1)这段逻辑,进行Stop the world Full GC,我们就称之为一般Full GC。

    我们先看看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));
    }
    

    很明显如果是CMS GC,则判断GCCause,如果是_java_lang_system_gc并且ExplicitGCInvokesConcurrent为true则返回true,这里又引出了另一个参数ExplicitGCInvokesConcurrent,如果配置了-XX:+ExplicitGCInvokesConcurrent,则返回true,进行并发Full GC,默认为false。

    并发Full GC

    我们接着先来看collect_mostly_concurrent,是如何进行并发Full GC。

    void GenCollectedHeap::collect_mostly_concurrent(GCCause::Cause cause) {
      assert(!Heap_lock->owned_by_self(), "Should not own Heap_lock");
    
      MutexLocker ml(Heap_lock);
      // Read the GC counts while holding the Heap_lock
      unsigned int full_gc_count_before = total_full_collections();
      unsigned int gc_count_before      = total_collections();
      {
        MutexUnlocker mu(Heap_lock);
        VM_GenCollectFullConcurrent op(gc_count_before, full_gc_count_before, cause);
        VMThread::execute(&op);
      }
    }
    
    

    最终通过VMThread来进行VM_GenCollectFullConcurrent中的void VM_GenCollectFullConcurrent::doit()方法来进行回收,这里代码有点多就不展开了,这里最终执行了一次Young GC来回收Young区,另外执行了下面这个方法

    void CMSCollector::request_full_gc(unsigned int full_gc_count, GCCause::Cause cause) {
      GenCollectedHeap* gch = GenCollectedHeap::heap();
      unsigned int gc_count = gch->total_full_collections();
      if (gc_count == full_gc_count) {
        MutexLockerEx y(CGC_lock, Mutex::_no_safepoint_check_flag);
        _full_gc_requested = true;
        _full_gc_cause = cause;
        CGC_lock->notify();   // nudge CMS thread
      } else {
        assert(gc_count > full_gc_count, "Error: causal loop");
      }
    }
    

    这里主要看_full_gc_requested设置成true 以及唤醒CMS background回收线程。
    大家可能了解过CMS GC有个后台线程一直在扫描,是否进行一次CMS GC,这个线程默认2s进行一次扫描,其中有个判断条件_full_gc_requested是否为true,如果为true,进行一次CMS GC,对Old和Perm区进行一次回收。

    一般Full GC

    一般的Full GC会走下面逻辑

    void GenCollectedHeap::collect_locked(GCCause::Cause cause, int max_level) {
      if (_preloading_shared_classes) {
        report_out_of_shared_space(SharedPermGen);
      }
      // Read the GC count while holding the Heap_lock
      unsigned int gc_count_before      = total_collections();
      unsigned int full_gc_count_before = total_full_collections();
      {
        MutexUnlocker mu(Heap_lock);  // give up heap lock, execute gets it back
        VM_GenCollectFull op(gc_count_before, full_gc_count_before,
                             cause, max_level);
        VMThread::execute(&op);
      }
    }
    

    通过VMThread来调用VM_GenCollectFull中的void VM_GenCollectFull::doit()方法来进行回收。

    void VM_GenCollectFull::doit() {
      SvcGCMarker sgcm(SvcGCMarker::FULL);
    
      GenCollectedHeap* gch = GenCollectedHeap::heap();
      GCCauseSetter gccs(gch, _gc_cause);
      gch->do_full_collection(gch->must_clear_all_soft_refs(), _max_level);
    }
    

    这里最终会通过GenCollectedHeap的do_full_collection方法进行一次Full GC,会回收Young、Old、Perm区,并且即使Old区使用的是CMS GC,也会对Old区进行compact,也就是MSC,标记-清除-压缩。

    总结

    System.gc()会触发Full GC,可以通过-XX:+DisableExplicitGC参数屏蔽System.gc(),在使用CMS GC的前提下,也可以使用-XX:+ExplicitGCInvokesConcurrent参数来进行并发Full GC,提升性能。
    不过,一般不推荐使用System.gc(),因为Full GC 耗时比较长,对应用影响较大,如前段时间的一个案例:依赖包滥用System.gc()导致的频繁Full GC
    并且也不建议设置-XX:+DisableExplicitGC,特别是在有使用堆外内存的情况下,如果堆外内存申请不到足够的空间,jdk会触发一次System.gc(),来进行回收,如果屏蔽了,最后一根救命稻草也就失效了,自然就OOM了。

    欢迎关注微信公共号

    相关文章

      网友评论

      本文标题:System.gc() 源码解读

      本文链接:https://www.haomeiwen.com/subject/bxqloxtx.html