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

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

3年前
989244

前言

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。

就到这了,欢迎留言支持

点赞收藏
RednaxelaFX
请先登录,查看4条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

从 Linux 内核角度探秘 JDK MappedByteBuffer

从 Linux 内核角度探秘 JDK MappedByteBuffer

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

4
4