性能文章>System.gc() 源码解读>

System.gc() 源码解读原创

2年前
7434012

介绍

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为javalangsystemgc。这里有个注意点就是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
  }
}

这个方法首先通过shoulddoconcurrentfullgc方法判断是不是进行一次并发Full GC,如果是则调用collectmostlyconcurrent方法,进行并发Full GC;如果不是则一般会走到collect(cause, n_gens() - 1)这段逻辑,进行Stop the world Full GC,我们就称之为一般Full GC。

我们先看看shoulddoconcurrentfullgc到底有哪些条件

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,如果是javalangsystemgc并且ExplicitGCInvokesConcurrent为true则返回true,这里又引出了另一个参数ExplicitGCInvokesConcurrent,如果配置了-XX:+ExplicitGCInvokesConcurrent,则返回true,进行并发Full GC,默认为false。

并发Full GC

我们接着先来看collectmostlyconcurrent,是如何进行并发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来进行VMGenCollectFullConcurrent中的void VMGenCollectFullConcurrent::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");
  }
}

这里主要看fullgcrequested设置成true 以及唤醒CMS background回收线程。
大家可能了解过CMS GC有个后台线程一直在扫描,是否进行一次CMS GC,这个线程默认2s进行一次扫描,其中有个判断条件fullgcrequested是否为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来调用VMGenCollectFull中的void VMGenCollectFull::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);
}

这里最终会通过GenCollectedHeapdofullcollection方法进行一次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了。

点赞收藏
分类:标签:
涤生

目前就职于某大型互联网公司基础架构部,主要负责微服务框架、服务治理、Serverless 等中间件研发相关工作。欢迎关注“涤生的博客”公共号。

请先登录,感受更多精彩内容
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

记一次线上RPC超时故障排查及后续GC调优思路

记一次线上RPC超时故障排查及后续GC调优思路

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

【全网首发】从源码角度分析一次诡异的类被加载问题

【全网首发】从源码角度分析一次诡异的类被加载问题

12
0