性能文章>关于JConsole里的“执行GC”按钮与System.gc()的关系>

关于JConsole里的“执行GC”按钮与System.gc()的关系原创

1年前
731133

前言

JConsole里的可以执行的gc
这里的GC是立即执行,还是与代码里写System.gc()一样,是由jvm决定什么时候来执行的?
执行的是minor gc,还是full gc,还是根据特定区域执行不同类型的gc?

正文

JConsole里“执行GC”的按钮背后通过JMX API去触发目标JVM进行GC。

代码可以参考这里的234行:

sun.tools.jconsole.MemoryTab.gc()


public void gc() {  
    new Thread("MemoryPanel.gc") {  
        public void run() {  
            ProxyClient proxyClient = vmPanel.getProxyClient();  
            try {  
                proxyClient.getMemoryMXBean().gc();  
            } catch (UndeclaredThrowableException e) {  
                proxyClient.markAsDead();  
            } catch (IOException e) {  
                // Ignore  
            }  
        }  
    }.start();  
}  

这里调用的JMX API是java.lang.management.MemoryMXBean.gc()

它背后在Oracle JDK / OpenJDK中的实现是sun.management.MemoryImpl.gc()


public void gc() {  
    Runtime.getRuntime().gc();  
}  

也就是说跟java.lang.System.gc()一样


public static void gc() {  
    Runtime.getRuntime().gc();  
}  

如果对System.gc()的认知正确的话,那么只需要知道JConsole里的“执行GC”做的是一模一样的事情。

================================================================

印象中我以前在哪里写过HotSpot对System.gc()的处理,一时找不出来了,再简单写写吧。

System.gc()在HotSpot VM里的大入口就在JVM_GC()函数


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

后续的执行路径有兴趣可以自己再看下去。

在HotSpot VM里,System.gc()默认触发的是一次full GC,stop-the-world,收集全堆。如果配置了+UseConcMarkSweepGC并且开启了+ExplicitGCInvokesConcurrent 或 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses,则System.gc()会触发一次CMS GC,并发模式,只收集old gen。如果开启了+DisableExplicitGC则System.gc()不执行任何GC就直接返回。

通常HotSpot VM会立即执行System.gc()触发的GC。但如果调用System.gc()的时候有线程正在JNI critical section中(也就是执行了JNI的xxxCritical系函数GetStringCriticalGetPrimitiveArrayCritical、而还没释放定住的资源ReleaseStringCritical
ReleasePrimitiveArrayCritical时),则HotSpot VM会先记下这个请求并让System.gc()返回,然后等所有JNI critical section都退出后再补一次full GC。如果配置了+UseConcMarkSweepGC并且开启了+GCLockerInvokesConcurrent的话则这种延迟触发的GC会是CMS GC,并发模式,只收集old gen。

JNI Specification 写道

After calling GetPrimitiveArrayCritical, the native code should not run for an extended period of time before it calls ReleasePrimitiveArrayCritical. We must treat the code inside this pair of functions as running in a “critical region.” Inside a critical region, native code must not call other JNI functions, or any system call that may cause the current thread to block and wait for another Java thread. (For example, the current thread must not call read on a stream being written by another Java thread.)
These restrictions make it more likely that the native code will obtain an uncopied version of the array, even if the VM does not support pinning. For example, a VM may temporarily disable garbage collection when the native code is holding a pointer to an array obtained via GetPrimitiveArrayCritical.

================================================================

在Oracle JRockit里,System.gc()触发的是nursery GC(如果选择了分代GC的话;如果选择的不是分代式GC算法则谈不上nursery还是old)。与HotSpot相同,可以通过一个参数禁用System.gc():-XXnoSystemGC。也可以通过另一个参数来强制System.gc()做full GC:-XXfullSystemGC。
JRockit R28里,禁用System.gc()的推荐参数是-XX:AllowSystemGC=false,而设定System.gc()触发full GC的参数是-XX:FullSystemGC=true。


在IBM JDK的JVM里,System.gc()同样可以禁用——使用-Xdisableexplicitgc参数。另外也有一些可以调节System.gc()触发的GC内容的参数,如-Xcompactexplicitgc、-Xnocompactexplicitgc之类。

由于JRockit的GC支持object pinning,它在接到System.gc()请求时总是可以立即响应,而不会受到JNI critical section的影响。它有可能会立即执行一次nursery GC(在JRockit里也叫YC,只收集nursery,也就是young gen对应到JRockit的叫法),或者是full GC(在JRockit里也叫OC收集全堆),或者就是GC(如果选择了不分代的模式,收集全堆);如果当前已经有并发GC正在执行中,它会选择等待这次并发GC结束然后启动一次新的GC,并等这次GC结束后再返回。

那么在HotSpot的fullgc中,是先执行minor gc,然后执行old gc还是两个并发的执行呢?

HotSpot VM的full GC就是full GC,用同一种收集算法处理整个堆,不需要用两个不同的收集器来收集young gen和old gen(以及perm gen)。

再次注意CMS GC(并发模式)不是full GC。CMS GC只收集old gen,并且可以与minor GC(通常是ParNew)并发进行。如果启用了 +CMSScavengeBeforeRemark 则在CMS remark阶段前会触发一次minor GC。

虽说不是必要的,但HotSpot VM可以有选择性在执行full GC前先执行一次minor GC。这发生在使用 +UseParallelGC 或 +UseParallelOldGC 时。有个叫 ScavengeBeforeFullGC 的参数默认是启用的,


product(bool, ScavengeBeforeFullGC, true,                    \  
        "Scavenge youngest generation before each full GC, " \  
        "used with UseParallelGC")  

当它启用并且当前在使用 +UseParallelGC 或 +UseParallelOldGC 时,触发full GC就会先用PSScavenge来收集一次young gen(也就是做一次minor GC),然后再用PSMarkSweep(如果用+UseParallelOldGC)或PSParallelCompact(如果用+UseParallelOldGC)来收集一到多次全堆。

可以像这样做实验观察到ScavengeBeforeFullGC开启与否的差别:


D:\>set JAVA_OPTS=-Xms256m -Xmx256m -XX:+PrintGCDetails -XX:+UseParallelOldGC  
  
D:\>groovysh  
Groovy Shell (1.8.4, JVM: 1.7.0_09)  
Type 'help' or '\h' for help.  
------------------------------------------------------------------------  
groovy:000> System.gc()  
[GC [PSYoungGen: 64953K->10851K(76480K)] 64953K->14882K(251264K), 0.0611647 secs] [Times: user=0.16 sys=0.00, real=0.06 secs]  
[Full GC (System) [PSYoungGen: 10851K->0K(76480K)] [ParOldGen: 4031K->14522K(17478  
4K)] 14882K->14522K(251264K) [PSPermGen: 13988K->13984K(28544K)], 0.2131337 secs] [Times: user=0.53 sys=0.00, real=0.22 secs]  
===> null  
groovy:000> quit  
Heap  
 PSYoungGen      total 76480K, used 7839K [0x00000000faab0000, 0x0000000100000000, 0x0000000100000000)  
  eden space 65600K, 11% used [0x00000000faab0000,0x00000000fb257cc0,0x00000000feac0000)  
  from space 10880K, 0% used [0x00000000feac0000,0x00000000feac0000,0x00000000ff560000)  
  to   space 10880K, 0% used [0x00000000ff560000,0x00000000ff560000,0x0000000100000000)  
 ParOldGen       total 174784K, used 14522K [0x00000000f0000000, 0x00000000faab0000, 0x00000000faab0000)  
  object space 174784K, 8% used [0x00000000f0000000,0x00000000f0e2e8d8,0x00000000faab0000)  
 PSPermGen       total 28544K, used 14052K [0x00000000eae00000, 0x00000000ec9e0000, 0x00000000f0000000)  
  object space 28544K, 49% used [0x00000000eae00000,0x00000000ebbb9120,0x00000000ec9e0000)  
  
D:\>set JAVA_OPTS=-Xms256m -Xmx256m -XX:+PrintGCDetails -XX:+UseParallelOldGC -XX:-ScavengeBeforeFullGC  
  
D:\>groovysh  
Groovy Shell (1.8.4, JVM: 1.7.0_09)  
Type 'help' or '\h' for help.  
---------------------------------------------------------------------------------  
groovy:000> System.gc()  
[Full GC (System) [PSYoungGen: 64954K->0K(76480K)] [ParOldGen: 0K->14522K(174784K)] 64954K->14522K(251264K) [PSPermGen: 13989K->13985K(28544K)], 0.4060576 secs] [Times: user=1.12 sys=0.00, real=0.41 secs]  
===> null  
groovy:000> quit  
Heap  
 PSYoungGen      total 76480K, used 7839K [0x00000000faab0000, 0x0000000100000000, 0x0000000100000000)  
  eden space 65600K, 11% used [0x00000000faab0000,0x00000000fb257cb8,0x00000000feac0000)  
  from space 10880K, 0% used [0x00000000ff560000,0x00000000ff560000,0x0000000100000000)  
  to   space 10880K, 0% used [0x00000000feac0000,0x00000000feac0000,0x00000000ff560000)  
 ParOldGen       total 174784K, used 14522K [0x00000000f0000000, 0x00000000faab0000, 0x00000000faab0000)  
  object space 174784K, 8% used [0x00000000f0000000,0x00000000f0e2eb20,0x00000000faab0000)  
 PSPermGen       total 28544K, used 14052K [0x00000000eae00000, 0x00000000ec9e0000, 0x00000000f0000000)  
  object space 28544K, 49% used [0x00000000eae00000,0x00000000ebbb92b8,0x00000000ec9e0000)  
  
D:\>  

这组实验里,头一次是(默认)开启了ScavengeBeforeFullGC的,在执行System.gc()的时候实际上执行了一次minor GC和一次full GC;后一次是显式关闭了ScavengeBeforeFullGC,在执行System.gc()时就只执行了一次full GC。

就到这了,欢迎留言支持

请先登录,再评论

这是R大真身吗,哭了哭了😭

21年前

牛逼

1年前

R大牛逼,给R大端茶喝。

1年前

为你推荐

不起眼,但是足以让你有收获的JVM内存分析案例
分析 这个问题说白了,就是说有些int[]对象不知道是哪里来的,于是我拿他的例子跑了跑,好像还真有这么回事。点该 dump 文件详情,查看相关的 int[] 数组,点该对象的“被引用对象”,发现所
从一起GC血案谈到反射原理
前言 首先回答一下提问者的问题。这主要是由于存在大量反射而产生的临时类加载器和 ASM 临时生成的类,这些类会被保留在 Metaspace,一旦 Metaspace 即将满的时候,就会触发 Fu
关于内存溢出,咱再聊点有意思的?
概述 上篇文章讲了JVM在GC上的一个设计缺陷,揪出一个导致GC慢慢变长的JVM设计缺陷,可能有不少人还是没怎么看明白的,今天准备讲的大家应该都很容易看明白 本文其实很犹豫写不写,因为感觉没有
协助美团kafka团队定位到的一个JVM Crash问题
概述 有挺长一段时间没写技术文章了,正好这两天美团kafka团队有位小伙伴加了我微信,然后咨询了一个JVM crash的问题,大家对crash的问题都比较无奈,因为没有现场,信息量不多,碰到这类问题我
又发现一个导致JVM物理内存消耗大的Bug(已提交Patch)
概述 最近我们公司在帮一个客户查一个JVM的问题(JDK1.8.0_191-b12),发现一个系统老是被OS Kill掉,是内存泄露导致的。在查的过程中,阴差阳错地发现了JVM另外的一个Bug。这个B
JVM实战:优化我的IDEA GC
IDEA是个好东西,可以说是地球上最好的Java开发工具,但是偶尔也会卡顿,仔细想想IDEA也是Java开发的,会不会和GC有关,于是就有了接下来对IDEA的GC进行调优 IDEA默认JVM参数: -
不起眼,但是足以让你收获的JVM内存案例
今天的这个案例我觉得应该会让你涨姿势吧,不管你对JVM有多熟悉,看到这篇文章,应该还是会有点小惊讶的,不过我觉得这个案例我分享出来,是想表达不管多么奇怪的现象请一定要追究下去,会让你慢慢变得强大起来,
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得