实战java虚拟机
垃圾回收器
如果说手机算法内存回收的方法论,那么垃圾回收器就是内存回收的具体实现。java虚拟机规范对垃圾回收器应该如何实现并没有任何规定,因此不同厂商、不同版本的虚拟机提供的垃圾回收器可能会有很大差异。(jdk1.7之后Hotspot虚拟机正式提供了G1回收器)
上图展示了作用于不同分带的回收器,如果两个回收器之前存在连线,那么说明他们可以搭配使用
串行回收器(Serial)
串行回收器是指使用单线程进行垃圾回收的处理器,每次会收时,串行回收器只有一个工作线程。对于并行能力较弱的计算机来说,川航回收器的专注性和独占性往往有更好的性能表现。
在串行回收器运行时,应用程序中所有线程都要停止工作(STOP THE WORLD),进行等待。它将造成非常糟糕的用户体验。
新生代串行回收器
新生代串行回收器使用的是复制算法,实现相对简单、逻辑处理特别高效、且没有线程切换开销。
使用-XX:+UserSerialGC可以指定使用新生代+老年代串行处理器
老年代串行回收器
l老年代串行回收器使用的是标记压缩算法。同样的,它也是一个串行的、独占的垃圾回收器。老年代的垃圾回收使用会比新生代的回收更耗时,因此,老年代串行回收器运行时,可能会停顿较长的时间。
使用 -XX:+UseSerialOldGC使用老年代串行回收器,同时它可以配合多种新生代回收器使用:
- -XX:+UserSerialGC: 新生代、老年代都是用串行回收器
- -XX:+UserParNewGC: 新生代使用ParNew回收器
- -XX:+UseParallelGC: 新生代使用ParllelGC回收器。
串行回收器的GC日志
并行回收器
并行回收器在串行回收器的基础上进行了改进,它使用多个线程同时进行垃圾回收。对于并行能力较强的计算机,可以有效的缩短垃圾回收所需的时间。
新生代ParNew回收器
ParNew是一个工作在新生代的并行垃圾回收器,它只是简单地将回收器多线程化,它的回收策略、算法以及参数和新生代串行回收器一样。
开始ParNew回收器可以使用以下参数:
- -XX:UseParNewGC:新生代使用ParNew回收器,老年代使用串行回收器
- -XX:UseConcMarkSweepGC: 新生代使用ParNew回收器,老年代使用CMS回收器。
ParNew回收器工作时可以使用-XX:ParallelGCThreads来指定线程的数量。设置的规则如下:
-当CPU数量小于等于8时,-XX:ParallelGCThreads=CPU数量
-当大于8时,-XX:ParallealGCThreads=3+((5*CPU数量)/8)
ParNew回收器的GC日志
ParallelGC回收器
ParallelGC是一个并行的回收器,它可以在新生代也可以在老年代进行垃圾回收。ParallelGC回收器有一个特点,它非常关注系统吞吐量。它提供如下参数来控制系统吞吐量:
- -XX:MaxGCPauseMills: 设计最大垃圾回收停顿时间。它是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。
- -XX:GCTimeRatio: 设置吞吐量大小。它是一个(0,,100) 的整数,假设GCTimeRatio的值为n,那么垃圾回收的时间将不超过1/(1+n),它的默认值是19,即默认不超过 1/(1+19) = 5%的时间用于垃圾回收。
- 还可以使用-XX:UseAdaptiveSizePolicy打开自适应GC策略,这种模式下,新生代大小,eden和survivor,晋升年老代的年龄等参数会自动调整,以达到堆空间、吞吐量和停顿点之间的平衡点。
新生代ParallelGC回收器
新生代Parallel回收器也是使用复制算法的回收器。可以使用如下参数启用:
- -XX:+UseParalleGC: 新生代使用ParallelGC,老年代使用串行回收器
- -XX:+UseParallelOldGC:新生代使用ParallelGC,老年代使用ParallelOldGC.
新生代ParallelOldGC回收器
- -XX:+UseParallelOldGC:新生代使用ParallelGC,老年代使用ParallelOldGC.
ParallelGC回收器的GC日志
CMS回收器
CMS是Concurrent Mark Sweep的缩写,意味并发标记清除。CMS回收器主要关注系统停顿时间。
并发是指垃圾收集线程和应用线程交替执行。
并行是指应用程序停止,多个线程执行垃圾回收。因此,并行回收器不是并发的,是独占的。而CMS回收器则是并发的,非独占的。
CMS工作原理
CMS回收器的工作过程相对于其他垃圾回收器,略显复杂。CMS工作时,主要步骤:
初始标记、并发标记、重新标记都是为了标记处出需要回收的对象。
并发清理是在标记完成之后,正式回收垃圾对象。
并发重置是指在来及回收完成之后,重新初始化CMS数据结构和数据,为下一次垃圾回收做准备。
并发标记、并发清理和并发重置是可以和应用程序一起执行的
预清理:是并发的,除了为正式清理做准备和检查之外,预清理还会尝试控制一次停顿时间。可以使用关闭开关-XX:CMSPreclarningEnabled,关闭或开启预处理。
CMS主要参数
启用CMS回收器参数是-XX:+UseConcMarkSweepGC。
- 线程数:
CMS是多线程回收器,设置合理的工作线程数量也对系统性能有重要影响。CMS默认的并发线程数是:(ParallelGCThreads + 3) /4 , 也可以通过-XX:ConcGCThreads或者-XX:ParallelCMSThreads参数设定。 - 回收阈值
CMS回收器不是独占式的回收器,所以在CMS回收过程中,应用程序仍然在不停的工作。所以在CMS回收期间, 应用程序又会产生新的垃圾, 这些垃圾在本次CMS垃圾回收期间是无法清除的。于此同时,因为应用程序没有中断,所以在CMS回收过程中,应当确应用程序保有足够的内存可用。因此CMS不会等到堆内存饱和了才进行垃圾回收,而是达到了一定的阈值便开始回收,以确保CMS回收过程中,应用程序正常运行。可以使用-XX:CMSInitiatingOccupancyFaction来指定老年代的空间使用率,默认值是68 。 - 内存碎片处理
CMS是一个基于标记清除算法的回收器,它将造成大量的内存碎片。
可以使用-XX:+UseCMSCompactAtFullCollection开关可以使CMS在垃圾收集完成后,进行一次内存碎片整理,内存碎片的整理不是并发进行的。可以是用-XX:CMSFullGCsBeforeCompaction参数指定多少次CMS回收后,进行一次内存压缩。 - class回收
使用CMS回收器时,如果想回收Perm区,默认情况下必须在Full GC时,才可以。
如果希望使用CMS回收Perm,则必须打开:-XX:CMSClassUnloadingEnabled开关。
CMS回收器的GC日志
G1回收器
G1回收器(Garbage-First)是jdk1.7中正式使用的 全新的垃圾回收器,它的出现是为了取代CMS回收器。
G1回收器有独特的垃圾回收策略,这和之前提到的回收器截然不同。
从分代上看,G1仍然是分代垃圾回收器,它会区分年轻代和老年代,依然有eden(伊甸园)和survivor区。
从堆结构上看,它并不要求整个eden,年轻代或者老年代都连续,它使用了分区算法。
G1的特点如下:
- 并行性:G1回收期间,有多个GC程序同时工作,有效利用多核计算能力
- 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行。
- 分代GC:G1仍然是一个分代的垃圾回收器。
- 空间整理:G1在回收过程中,会进行适当的对象移动,不像CMS只是简单的进行标记清除,在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效的复制对象,减少空间碎片。
- 可预见性:由于分区的原因,G1可以只选取部分分区进行内存回收,这样缩小了回收的范围,这样对于全局停顿(STOP THE WORLD)有很好的控制。
G1内存划分和收集过程
G1收集器将堆进行分区,划分为一个个的区域,每次收集只收集其中几个区域,以此来控制垃圾回收产生的停顿时间。
回收过程包含以下过程:
- 新生代GC
- 并发标记周期
- 混合收集
- 如果需要,可能会进行一次Full GC
G1的新生代GC
新生代GC主要工作是回收Eden和Survivor区。一旦Eden区被占满,新生代GC就会启动。回收后:
所有的Eden区被清空,Survivor会被收集一部分数据,但至少还存在一个Survivor ,另外和其他收集器的不同的是:老年代的区域增多(此部分是Eden和survivor的对象晋升到老年代)
打开-XX:+PrintGCDetails,查看日志
0.192: [GC pause (young), 0.00234057 secs]
[Eden: 2048K(2048K)->0B(1024K) Survivors: 0B->1024K Heap: 2048K(100M)->1248K(100M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
和其他回收器相比,G1的日志非常丰富,由日志可知:Eden被清空由2048 ->0,而Survivor由0 ->1024,整个堆合计100m,由2048k -> 1248k。 在应用程序启动0.192秒后,发生了第一次新生代GC。
G1并发标记周期
并发标记周期的主要工作流程如下图:
- 初始标记:标记从根节点直接可达的对象。这个阶段会伴随一次新生代GC**(STW)**,由新生代的GC特点可知,初始标记后,Eden清空,并部分复制到Survivor,它的日志结构如下:
1.963: [GC pause (young) (initial-mark), 0.00462562 secs]
....
[Eden: 1024K(1024K)->0B(1024K) Survivors: 1024K->1024K Heap: 46M(100M)->46M(100M)]
[Times: user=0.03 sys=0.00, real=0.01 secs]
- 根区域扫描:扫描由Survivor区可直接可达的老年代区域,并标记这些可达的对象。这个过程是可以和应用程序并发执行的。(根区域扫描依赖survivor区域的对象,而新生代GC会修改这个区域,所以此阶段不能和新生代GC同时执行)。它的日志结构如下:
1.968: [GC concurrent-root-region-scan-start]
1.969: [GC concurrent-root-region-scan-end, 0.0005720]
- 并发标记:扫描整个堆的存活对象,并做好标记。 这是一个并发过程,可以被新生代GC打断。它的日志结构如下:
1.969: [GC concurrent-mark-start]
.....
2.005: [GC concurrent-mark-end, 0.0363192 sec]
- 重新标记:这个阶段会使得应用程序停顿(STW)。由于并发标记过程中,应用程序依然在执行,因此标记结果可能需要进行修正,所以在此对上一次的标记结果进行补充。它的日志结果如下:
2.005: [GC remark 2.006: [GC ref-proc, 0.0000070 secs], 0.0009368 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
- 独占清理:这个阶段会使得应用程序停顿(STW)。它将计算各个区域的存货对象和GC回收比例并进行排序,识别可供混合回收的区域。在这个阶段,还会更新记忆集(Remembered Set)。该阶段给出了需要被混合回收的区域进行了标记,在混合回收阶段,会用到此信息。它的日志结构如下:
2.006: [GC cleanup 46M->46M(100M), 0.0006690 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
- 并发清理:这里会识别并清理完全空闲的区域。它是并发的清理,不会引起停顿。它的日志结构如下:
2.821: [GC concurrent-cleanup-start]
2.821: [GC concurrent-cleanup-end, 0.0000093]
并发标记后内存变化:
并发标记周期执行前后最大的不同是在该阶段后,系统增加了一些标记G区域。这些区域标记,是因为它们内部的垃圾比例较高,因此希望在此后的混合GC进行收集(注意:并发标记周期阶段并没有正式收集这些区域),这些要回收的区域会被G1记录在一个Collection Sets(回收集)集合中。
G1混合回收
在并发标记周期中,虽然有部分对象会回收,但是总体上,它的回收比例是比较低的。但在并发标记周期后,G1已经明确知道哪些区域含有比较多的垃圾对象,在混合回收阶段就可以针对这些区域进行回收。当然,G1会优先回收垃圾比例较高的区域,因为回收这些区域的性价比比较高。这也是G1名字的由来,G1全程为Garbage First Garbage Collector,直译为垃圾优先的垃圾回收器。
这个阶段叫做混合回收,是因为在这个阶段,即会执行正常的年轻带GC,又会选取一些被标记的老年代区域进行回收,同时处理了新生代和老年代。
如图,由于新生代GC所以所有的Eden区被清空,此外两块标记为G的垃圾比例最高的区域被清理,被清理区域中存货对象会被移动到其他区域,这样的好处是减少了空间碎片。
混合回收的日志结构如下:
2.409: [GC pause (mixed), 0.01857246 secs]
.....
[Eden: 1024K(1024K)->0B(1024K) Survivors: 1024K->1024K Heap: 53M(100M)->52M(100M)]
[Times: user=0.01 sys=0.02, real=0.02 secs]
混合GC会执行多次,直到回收了足够的内存空间,然后它会触发一次新生代GC.新生代GC后,又可能发生一次并发标记周期的处理,最后,又会引起混合GC的执行。
必要时的FullGC
并发收集让应用程序和GC线程交替工作,因此在特别繁忙的情况下无可避免的会发生回收过程中内存不足的情况,当遇到这种情况,G1会转入一个Full GC 进行回收。
例如:当G1在并发标记时,由于老年代被快速填充,那么G1会终止并发标记(concurrent-mark-abort),转入一个Full GC,日志结构如下:
5.934: [GC concurrent-mark-start]
5.934: [Full GC 98M->552K(100M), 0.0221041 secs]
[Times: user=0.03 sys=0.00, real=0.02 secs]
5.956: [GC concurrent-mark-abort]
G1日志
下例给出一个完整的G1新生代GC日志,并进行分析:
//表示在应用程序启动2.083秒后发生了第一次新生代GC,这是在初始标记阶段发生的,耗时0.0037s。
2.083: [GC pause (young) (initial-mark), 0.00372709 secs]
[Parallel Time: 3.4 ms] //表示所有GC线程总的花费时间3.4ms
//四个数字,表示4个线程;表示四个线程都在应用程序启动2083.5ms后开启;
[GC Worker Start (ms): 2083.5 2083.5 2083.5 2083.5
Avg: 2083.5, Min: 2083.5, Max: 2083.5, Diff: 0.0]
//根扫描时,每一个线程耗时ms
[Ext Root Scanning (ms): 0.3 0.4 0.4 0.3
Avg: 0.4, Min: 0.3, Max: 0.4, Diff: 0.1]
//更新remember set的耗时
[Update RS (ms): 2.1 2.4 2.8 2.2
Avg: 2.3, Min: 2.1, Max: 2.8, Diff: 0.7]
//由于在系统运行过程中,对象引用关系是可能时刻变化的,为了更效的跟踪这些引用关系,避免扫描整个堆,
会将这些记录记录到Update Buffer中,而Processed Buffers就是处理这些数据的 。
[Processed Buffers : 4 3 3 2
Sum: 12, Avg: 3, Min: 2, Max: 4, Diff: 2]
//扫描remember set的耗时
[Scan RS (ms): 0.0 0.0 0.0 0.0
Avg: 0.0, Min: 0.0, Max: 0.0, Diff: 0.0]
//正式回收时,G1会对回收区域的存活对象进行疏散,即放置到其他区域中,此时需要对象的复制。
[Object Copy (ms): 0.9 0.6 0.2 0.9
Avg: 0.7, Min: 0.2, Max: 0.9, Diff: 0.7]
//写出GC工作线程终止信息:
[Termination (ms): 0.0 0.0 0.0 0.0
Avg: 0.0, Min: 0.0, Max: 0.0, Diff: 0.0]
[Termination Attempts : 1 1 1 1
Sum: 4, Avg: 1, Min: 1, Max: 1, Diff: 0]
//显示了GC工作线程的完成时间
[GC Worker End (ms): 2086.9 2086.9 2086.9 2086.9
Avg: 2086.9, Min: 2086.9, Max: 2086.9, Diff: 0.0]
//显示了GC工作线程的存活时间
[GC Worker (ms): 3.4 3.4 3.4 3.4
Avg: 3.4, Min: 3.4, Max: 3.4, Diff: 0.0]
//GC线程花费在其他任务中的耗时(这部分时间非常少)
[GC Worker Other (ms): 0.1 0.1 0.1 0.1
Avg: 0.1, Min: 0.1, Max: 0.1, Diff: 0.0]
//清空CardTable的时间,remember set就是靠CardTable来记录哪些是存活对象的。
[Clear CT: 0.0 ms]
//其他几个任务耗时
[Other: 0.2 ms]
[Choose CSet: 0.0 ms] //选择Collection Sets
[Ref Proc: 0.2 ms] //处理弱引用,软引用时间
[Ref Enq: 0.0 ms] //弱引用,软引用入队时间
[Free CSet: 0.0 ms] //释放被回收的CSet区域的时间,包括它们的RS
//GC后的整体情况:eden被清空,Survivor没有使用量没有变化,堆释放了1m空间,用户CPU耗时0.20,实际耗时0.04
[Eden: 1024K(1024K)->0B(1024K) Survivors: 1024K->1024K Heap: 46M(100M)->45M(100M)]
[Times: user=0.20 sys=0.00, real=0.07 secs]
对象内存分配与回收的一些细节问题
禁用System.gc()
默认情况下,使用System.gc()会显示的触发Full GC,同时对新生代和老年代进行回收。而一般情况下,垃圾回收应该是系统自动进行的,无需手动触发。过于频繁的触发垃圾回收对系统性能并没有好处。查看System.gc()的逻辑:
//1.System
public final class System {
......
public static void gc() {
Runtime.getRuntime().gc();
}
}
//2.Runtime
public class Runtime {
public native void gc();
}
Runtime.gc()调用的是一个本地方法,最终实现在jvm.cpp中:openJDK - jvm.cpp
//3.本地方法gc()
JVM_ENTRY_NO_ENV(void, JVM_GC(void))
JVMWrapper("JVM_GC");
if (!DisableExplicitGC) {
Universe::heap()->collect(GCCause::_java_lang_system_gc);
}
JVM_END
虚拟机提供一个参数-XX:+DisableExplicitGC来控制是否手动触发GC,如果设置了,则System.gc()相当于调用了一个空函数。
System.gc()并发回收
默认情况下,即使System.gc()生效,它会使用传统的Full GC方式回收整个堆,而会忽略参数中的-XX:+UserConcMarkSweepGC 或者-XX:+UseG1GC,
只用使用参数 -XX:+ExplicitGCInvokesConcurrent打开开关后,System.gc()这种显示的使用并发方式进行回收。
并行gc前额外触发新生代GC
对于并行处理器(-XX:+UseParallelGC -XX:+UseParallelOldGC),每次Full GC之前都会伴随着一次新生代GC.
[GC [PSYoungGen: 2956K->320K(3584K)] 2956K->2368K(10432K), 0.0056631 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System) [PSYoungGen: 320K->0K(3584K)] [ParOldGen: 2048K->2240K(6848K)] 2368K->2240K(10432K) [PSPermGen: 3664K->3663K(21248K)], 0.0291518 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
这样做的目的是,先将新生代进行一次收集,避免所有的工作都交给Full GC,从而尽可能的缩短一次停顿。
如果不需要改特性, 则使用命令: -XX:-ScavengeBeforeFullGC
对象何时进入老年代
一般情况下,对象首次创建时,会被放置在Eden区(伊甸园),如果没有GC介入,这些对象不会离开Eden区。 当对象的年龄达到一定的大小,就自然可以离开年轻代,进入年老代,此事件称为“晋升”。对象的年龄是由对象经历的GC次数决定的。新生代的对象每经历一次GC,如果它没有被回收,则年龄就加1.
- -XX:MaxTenuringThreshold
虚拟机提供参数,来设置年轻带对象的最大年龄, 默认是15. - -XX:TargetSurvivorRatio
默认值50.它表示如果survivor区超过了TargetSurvivorRatio设置的比例,则会使用更小的age晋升到老年代。 - PretenureSizeThreshold
除了年龄外,对象的体积也会影响对象晋升。如果对象体积过大,新生代无论eden或者survivor区都无法容纳这个对象,自然这个对象就无法存在在新生代,有可能直接晋升到老年代。-XX:MaxTenuringThreshold,用来设置对象直接晋升到老年代的大小阈值,但是是字节 。这个参数值针对串行回收器和ParNew回收器有效
在TLAB上分配对象
TLAB全程Thread Local Allocation Buffer,线程本地分配缓存。它的作用是为了加速对象分配而生的。
由于对象一般会分配在堆上,而堆是全局共享的 。而才同一时刻,可能会有多个线程在堆申请空间,因此每次对象分配都必须进行同步,激烈的竞争会导致分配效率的下降。因此java虚拟机就是用TLAB这种线程专属的区间来避免线程冲突,提高对象分配的效率。TLAB本身占用了eden区间。在TLAB启用的情况下(默认启用),虚拟机会为每个线程TLAB空间。
下例通过开启和关闭TLAB来比较性能差异:
public class UseTLAB {
public static void alloc(){
byte[] b = new byte[2];
b[0] = 1;
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int i = 0 ;i < 10000000 ; i++){
alloc();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
结果::
-XX:+UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server
118
-XX:-UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server
224
-BackgroundCompilation :禁止后台编译
-DoEscapeAnalysis :禁用逃逸分析
常用GC参数
串行回收器相关参数
- -XX:+UseSerialGC: 在新声代和老年代使用串行回收器
- -XX:SurvivorRatio: 设置eden/survivor-from 的比例,默认值8
- -XX:PretenureSizeThreshold: 设置大对象进入老年代的阈值,单位字节。
- -XX:MaxTenuringThreshold: 对象进入老年代的最大年龄,默认15
- XX:TargetSurvivorRatio : 默认值50.它表示如果survivor区超过了TargetSurvivorRatio设置的比例,则会使用更小的age晋升到老年代。
并行回收器相关
- -XX:+UseParNewGC: 新生代使用并行回收器
- -XX:+UseParallelOldGC: 老年代使用并行回收器
- -XX:ParallelGCThreads: 用于垃圾回收的线程数,
- -当CPU数量小于等于8时,-XX:ParallelGCThreads=CPU数量
- -当大于8时,-XX:ParallealGCThreads=3+((5*CPU数量)/8)
- -XX:MaxGCPauseMills:最大垃圾回收停顿时间
- -XX:GCTimeRatio:设置吞吐量大小。它是一个(0,,100) 的整数,假设GCTimeRatio的值为n,那么垃圾回收的时间将不超过1/(1+n),它的默认值是19,即默认不超过 1/(1+19) = 5%的时间用于垃圾回收。
- XX:UseAdaptiveSizePolicy :打开自适应GC策略,这种模式下,新生代大小,eden和survivor,晋升年老代的年龄等参数会自动调整,以达到堆空间、吞吐量和停顿点之间的平衡点。
CMSh回收器相关
- -XX:+UseConcMarkSweepGC:新生代使用并行回收器手机,老年代使用CMS串行回收器
- -XX:ParallelCMSThreads: 设置CMS线程数量
- -XX:CMSInitiatingOccupancyFaction:来指定老年代的空间使用率,默认值是68 ,后触发CMS手机
- -XX:+UseCMSCompactAtFullCollection: 设置CMS垃圾回收完成后是否需要内存整理
- -XX:CMSFullGCsBeforeCompaction: 设置多少次垃圾回收后,进行一次内存压缩。
- -XX:+CMSClassUnloadingEnabled: 允许对类元数据区进行回收
- -XX:+CMSInitiatingPermOccupancyFaction: 当永久代占用到100%时,启动CMS回收(前提是CMSClassUnloadingEnabled开启了)
- XX:CMSInitiatingOccupancyOnly: 只有达到阈值时才进行CMS回收
与G1回收器相关
- -XX:+UseG1GC: 使用G1回收器
- -XX:MaxGCPauseMills: 最大gc停顿时间
- -XX:MaxPauseIntervalMills: 设置停顿间隔时间
TLAB相关
- -XX:+UseTLAB: 开启TLAB分配
- -XX:PrintTLAB:打印TLAB相关分配信息
- -XX:TLABSize: 设置TLAB大小
- XX:+ResizeTLABSize: 自动调整TLAB大小
其他参数
- -XX:+DisableExplicitGC: 禁止显示GC
- -XX:+ExplicitGCInvokesConcurrent: 并发方式处理显示GC
- -XX:++HeapDumpBeforeFullGC: 实现在Full GC前dump。
- -XX:+HeapDumpAfterFullGC: 实现在Full GC后dump。(HeapDumpPath:设置Dump保存的路径)