Java基础-04丨System.gc()

Posted by jiefang on November 27, 2019

System.gc()

介绍

System.gc(),是JDK提供的触发Full GC的一种方式,会触发Full GC,其间会stop the world,对业务影响较大,一般情况下不会直接使用。

  • system.gc其实是做一次full gc;
  • system.gc会暂停整个进程;
  • system.gc一般情况下我们要禁掉,使用-XX:+DisableExplicitGC
  • system.gc在cms gc下我们通过-XX:+ExplicitGCInvokesConcurrent来做一次稍微高效点的GC(效果比Full GC要好些);
  • system.gc最常见的场景是RMI/NIO下的堆外内存分配等;

    源码

JDK实现

System.gc()
1
2
3
    public static void gc() {
        Runtime.getRuntime().gc();
    }
Runtime.gc()
1
2
    //java本地方法
    public native void gc();

JVM实现

Runtime.c#Java_java_lang_Runtime_gc()
1
2
3
4
5
    JNIEXPORT void JNICALL
    Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
    {
        JVM_GC();
    }
jvm.cpp#JVM_GC()
1
2
3
4
5
6
7
    JVM_ENTRY_NO_ENV(void, JVM_GC(void))
      JVMWrapper("JVM_GC");
      //对应配置-XX:+DisableExplicitGC
      if (!DisableExplicitGC) {
        Universe::heap()->collect(GCCause::_java_lang_system_gc);
      }
    JVM_END

由heap()调用collect方法,具体是哪种heap,需要看gc算法,如常用的CMS GC的对应的heap是GenCollectedHeap

genCollectedHeap.cpp#collect()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    void GenCollectedHeap::collect(GCCause::Cause cause) {
      //判断是否是并发Full GC
      if (should_do_concurrent_full_gc(cause)) {
    #if INCLUDE_ALL_GCS
        // mostly concurrent full collection
        //进行并发Full GC
        collect_mostly_concurrent(cause);
    #else  // INCLUDE_ALL_GCS
        ShouldNotReachHere();
    #endif // INCLUDE_ALL_GCS
      } 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
      }
    }
genCollectedHeap.cpp#should_do_concurrent_full_gc()
1
2
3
4
5
6
 //判断是不是进行一次并发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垃圾收集器,且由System.gc()引起,且设置-XX:ExplicitGCInvokesConcurrent参数返回true。

涉及-XX:ExplicitGCInvokesConcurrent参数配置。

使用

堆外内存常配合使用System.gc()

堆外内存主要针对java.nio.DirectByteBuffer,这些对象的创建过程会通过Unsafe接口直接通过os::malloc来分配内存,然后将内存的起始地址和大小存到java.nio.DirectByteBuffer对象里,这样就可以直接操作这些内存。这些内存只有在DirectByteBuffer`回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存。

总结

System.gc()会触发Full GC,可以通过-XX:+DisableExplicitGC参数屏蔽System.gc(),在使用CMS GC的前提下,也可以使用-XX:+ExplicitGCInvokesConcurrent参数来进行并发Full GC,提升性能。

面试题

在 Java 中,如何手动的让 GC 进行垃圾回收?
  • System.gc()
  • jmap -dump:livejmap -histo:live
System.gc 是 Full Gc 吗?

System.gc 其实是做一次 full gc。这一点可以根据 DisableExplicitGC 的注释说明来看。

System.gc 的危害是什么?

System.gc 是一个 Full gc,所以会暂停整个进程。如果进程经常被频繁暂停,就要注意超时、并发等问题。

如何避免 System.gc ?
  • 通过 -XX:+DisableExplicitGC 禁掉 System.gc。
  • 如果是CMS垃圾收集器可以使用-XX:+ExplicitGCInvokesConcurrent参数使Full GC变为并发CMS GC。
System.gc 的 Full Gc 如何做到暂停整个进程?

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

并行的 Full GC 是否有了解?

并行 Full GC 也同样会做 YGC 和 CMS GC,但是效率高就搞在 CMS GC 是走的 background 的,整个暂停的过程主要是 YGC、CMS_initMark、CMS_remark 几个阶段。

background 顾名思义是在后台做的,也就是可以不影响正常的业务线程跑,触发条件比如说 old 的内存占比超过多少的时候就可能触发一次 background 式的 cms gc,这个过程会经历 CMS GC 的所有阶段,该暂停的暂停,该并行的并行,效率相对来说还比较高,毕竟有和业务线程并行的 gc 阶段;

而 foreground 则不然,它发生的场景比如业务线程请求分配内存,但是内存不够了,于是可能触发一次 cms gc,这个过程就必须是要等内存分配到了线程才能继续往下面走的,因此整个过程必须是STW的,因此 CMS GC 整个过程都是暂停应用的,但是为了提高效率,它并不是每个阶段都会走的,只走其中一些阶段,这些省下来的阶段主要是并行阶段。