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为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);
}

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

原文发布于微信公众号 - 涤生的博客(disheng_yq)

原文发表时间:2018-01-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阮一峰的网络日志

JavaScript 内存泄漏教程

一、什么是内存泄漏? 程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。 对于持续运行的服务进程(daemon),必须及...

3716
来自专栏微服务生态

从0到1起步-跟我进入堆外内存的奇妙世界

堆外内存一直是Java业务开发人员难以企及的隐藏领域,究竟他是干什么的,以及如何更好的使用呢?那就请跟着我进入这个世界吧。

962
来自专栏岑玉海

Spark调优

因为Spark是内存当中的计算框架,集群中的任何资源都会让它处于瓶颈,CPU、内存、网络带宽。通常,内存足够的情况之下,网络带宽是瓶颈,这时我们就需要进行一些调...

3878
来自专栏令仔很忙

OutOfMemoryError异常----Java堆溢出

在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(下面都叫OOM)异常的肯能,下面就通过...

1152
来自专栏武培轩的专栏

迅雷面经汇总

实现多态的技术称为 :动态绑定,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

1361
来自专栏ml

Redis入门学习笔记一

Redis 简要描述:   1.  Redis 是啥 ?        Redis 英文名称全称为: Remote Dictionary Server ,中译为...

3175
来自专栏JAVA烂猪皮

JVM难学?那是因为你没认真看完这篇文章

JAVA程序运行与虚拟机之上,运行时需要内存空间。虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理。

1103
来自专栏蓝天

HDFS块文件和存放目录的关系

详情请参见DatanodeUtil.java中的函数idToBlockDir(File root, long blockId)。 如果block文件没有放在...

620
来自专栏MasiMaro 的技术博文

OLEDB 简单数据查找定位和错误处理

在数据库查询中,我们主要使用的SQL语句,但是之前也说过,SQL语句需要经历解释执行的步骤,这样就会拖慢程序的运行速度,针对一些具体的简单查询,比如根据用户ID...

1662
来自专栏Spark学习技巧

spark调优系列之内存和GC调优

本文基于spark1.6讲解。 一,基本概述 调优内存的使用主要有三个方面的考虑:对象的内存占用量(你可能希望整个数据集都适合内存),访问这些数据的开销,垃圾...

9269

扫码关注云+社区

领取腾讯云代金券