JDK1.8中使用jmap -heap pid
上面会出现Parallel GC
jmap -heap 18378 Attaching to process ID 18378, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.261-b12 using thread-local object allocation. Parallel GC with 4 thread(s) ###
JVM垃圾收集器的发展历史中,咱们并无找到
Parallel GC
,那么它到底表明什么?
-XX:+UseParallelGC
参数来启用Parallel Scavenge
和PSMarkSweep(Serial Old)
收集器组合进行垃圾收集。(图上能够找到)-XX:+UserParallelOldGC
参数来启用Parallel scavenge
和Parallel Old
收集器组合收集。(图上能够找到)Parallel Scavenge收集器(下称PS收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不一样,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它容许较长时间的STW换取总吞吐量最大化。
在Parallel Scavenge收集器架构中自己有PS MarkSweep收集器来进行老年代收集,但因为PS MarkSweep与Serial Old实现很是接近,所以官方的许多资料都直接以Serial Old代替PS MarkSweep进行讲解。使用
-XX:+UseParallelGC
参数即是开启PSScavenge
和PSMarkSweep
的组合,便是只有Young GC
是并行的,Full GC
仍然是串行,使用标记-整理
算法。java
后来开发者开发了基于LISP2算法的并行版的Full GC收集器来收集整个GC堆,名为PSCompact
。使用-XX:+UseParallelOldGC
参数来即是开启PSScavenge
和PSCompact
的组合,将Young GC
和Full GC
都并行化了。
新生代并行回收器,内存分布使用的复制算法。
Parallel Scavenge
主要关注的是应用的吞吐量,而其余收集器关注的主要是尽量的缩短STW(stop the word)的时间。
吞度量=t1/(t1+t2) t1运行用户代码的总时间 t2运行垃圾收集的总时间 好比,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
Parallel Scavenge
收集器提供了两个参数来用于精确控制吞吐量,一是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis
参数,二是控制吞吐量大小的-XX:GCTimeRatio
参数
参数的值是一个大于0的毫秒数,收集器将尽量的保证回收耗费的时间不超过设定的值,可是,并非越小越好,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的,若是设置的值过小,将会致使频繁GC,这样虽然GC停顿时间下来了,可是吞吐量也下来了。好比收集500MB时候,须要每10秒收集一次,每次回收耗时100ms;若是收集300MB的时候,须要每5秒收集一次,每次回收耗时70ms,虽然每次回收耗时更少,可是工做频次提升,致使吞吐量反而下降了。
参数的值是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,默认值是99,就是容许最大1%(即1/(1+99))的垃圾收集时间。
Parallel Scavenge有个重要的特性,是支持GC自适应的调节策略,使用-XX:UseAdaptiveSizePolicy参数开启,开启以后,虚拟机会根据当前系统运行状况收集监控信息,动态调整新生代的比例、老年大大小等细节参数,以提供最合适的停顿时间或最大的吞吐量。开启这个参数以后,就不须要再设置新生代大小,Eden与S0/S1的比例等等参数。
Parallel Old GC
在Parallel Scavenge
和Parallel Old
收集器组合中,负责Full GC,是一个并行收集器,其在整理年轻代的时候,使用与Parallel Scavenge GC同样的常规“复制”算法,可是在整理老年代的时候,是使用的基于“标记-整理”算法优化的“Mark–Summary-Compaction”算法。
算法包含三个部分
首先将老年代的内存,划分为大小固定的多个连续Region,当标记完存活对象以后,统计每一个Region的存活对象数量。Mark阶段采用串行标记全部从GC Roots可直达的对象,而后并行标记全部存活的对象。
某个Region的密度 = 存活对象的内存大小 / Region内存大小。由于每次整理会将存活的对象向Old区的左侧移动,而对象存活越久,理论上就越不容易被回收,因此通过屡次整理以后,左侧Region中的对象更偏向于稳定、“长寿”,便是左侧Region的密度更大。Summary阶段,算法采用以空间换时间的优化方式,针对一个密度很大的Region,好比95%的空间是存活对象,只有断断续续5%的空间是未使用的,那么算法认为这个Region不值得被整理,便是选择浪费掉这5%的空间,以节省整理操做的时间开销。在Sumamry阶段,首先从左至右计算各个Region的密度,直到找到一个point,这个point左侧的Region都不值得整理,右侧的Region须要整理。point左侧的Region被称为dense prefix,这个区域内的对象都不会被移动。Summary阶段是一个串行执行的阶段。
Compaction阶段利用Summary阶段的统计数据,针对须要整理的部分,采用“整理”算法进行并行操做。
ScavengeBeforeFullGC
是Parallel GC
套装中(两种组合都生效)的一个参数,默认是开启的,做用是在一次Full GC以前,先触发一次Young GC来清理年轻代,以下降Full GC的STW耗时(Young GC会清理Young GC中非存活的对象,减小Full GC中,标记存活对象的工做量)。举个例子,使用System.gc()触发Full GC,能够看到日志以下:算法
2020-03-01T13:38:30.496-0800: [GC (System.gc()) [PSYoungGen: 37274K->1392K(46080K)] 78234K->42360K(97280K), 0.0033397 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] 2020-03-01T13:38:30.500-0800: [Full GC (System.gc()) [PSYoungGen: 1392K->0K(46080K)] [ParOldGen: 40968K->1225K(51200K)] 42360K->1225K(97280K), [Metaspace: 4876K->4876K(1056768K)], 0.0113851 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
第一次GC为一次Young GC,能够看到是由System.gc()触发的,而后紧跟着是一次Full GC。添加
-XX:-ScavengeBeforeFullGC
参数以后,日志就变为只有一条Full GC的日志:多线程
2020-03-01T14:26:05.562-0800: [Full GC (System.gc()) [PSYoungGen: 37274K->0K(46080K)] [ParOldGen: 40960K->1225K(51200K)] 78234K->1225K(97280K), [Metaspace: 4882K->4882K(1056768K)], 0.0127785 secs] [Times: user=0.05 sys=0.01, real=0.01 secs]
对于常规收集器来讲,当Eden区没法分配内存时,便会触发一次Young GC,可是对于Parallel GC有点变化:
举个例子:架构
public class TestApp { public static void main(String[] args) throws InterruptedException { allocM(10); allocM(10); allocM(10); allocM(20); Thread.sleep(1000); } private static byte[] allocM(int n) throws InterruptedException { byte[] ret = new byte[1024 * 1024 * n]; System.out.println(String.format("%s: Alloc %dMB", LocalDateTime.now().toString(), n)); Thread.sleep(500); return ret; } }
JVM参数为:
-Xms100m -Xmx100m -Xmn50m -XX:SurvivorRatio=8 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+UseParallelOldGC
,运行起来,打印日志以下:
2020-03-01T16:36:13.027: Alloc 10MB 2020-03-01T16:36:13.548: Alloc 10MB 2020-03-01T16:36:14.061: Alloc 10MB 2020-03-01T16:36:14.577: Alloc 20MB Heap PSYoungGen total 46080K, used 38094K [0x00000007bce00000, 0x00000007c0000000, 0x00000007c0000000) eden space 40960K, 93% used [0x00000007bce00000,0x00000007bf333878,0x00000007bf600000) from space 5120K, 0% used [0x00000007bfb00000,0x00000007bfb00000,0x00000007c0000000) to space 5120K, 0% used [0x00000007bf600000,0x00000007bf600000,0x00000007bfb00000) ParOldGen total 51200K, used 20480K [0x00000007b9c00000, 0x00000007bce00000, 0x00000007bce00000) object space 51200K, 40% used [0x00000007b9c00000,0x00000007bb000010,0x00000007bce00000) Metaspace used 4879K, capacity 5012K, committed 5248K, reserved 1056768K class space used 527K, capacity 564K, committed 640K, reserved 1048576K
能够看到第4行,分配20M内存时,Eden区已经不足20M空余内存了,整个年轻代加起来都不够20M了,可是并无触发Young GC,而是继续执行,知道程序结束前,打印堆的状况,咱们能够看到20M内存是分配到了老年代中。修改代码,将最后一个
allocM(20);
改为allocM(5);
,从新执行,获得日志以下:less
2020-03-01T16:39:56.375: Alloc 10MB 2020-03-01T16:39:56.896: Alloc 10MB 2020-03-01T16:39:57.408: Alloc 10MB {Heap before GC invocations=1 (full 0): PSYoungGen total 46080K, used 37274K [0x00000007bce00000, 0x00000007c0000000, 0x00000007c0000000) eden space 40960K, 91% used [0x00000007bce00000,0x00000007bf266a08,0x00000007bf600000) from space 5120K, 0% used [0x00000007bfb00000,0x00000007bfb00000,0x00000007c0000000) to space 5120K, 0% used [0x00000007bf600000,0x00000007bf600000,0x00000007bfb00000) ParOldGen total 51200K, used 0K [0x00000007b9c00000, 0x00000007bce00000, 0x00000007bce00000) object space 51200K, 0% used [0x00000007b9c00000,0x00000007b9c00000,0x00000007bce00000) Metaspace used 4882K, capacity 5012K, committed 5248K, reserved 1056768K class space used 526K, capacity 564K, committed 640K, reserved 1048576K 2020-03-01T16:39:57.910-0800: [GC (Allocation Failure) [PSYoungGen: 37274K->1328K(46080K)] 37274K->1336K(97280K), 0.0033380 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] Heap after GC invocations=1 (full 0): PSYoungGen total 46080K, used 1328K [0x00000007bce00000, 0x00000007c0000000, 0x00000007c0000000) eden space 40960K, 0% used [0x00000007bce00000,0x00000007bce00000,0x00000007bf600000) from space 5120K, 25% used [0x00000007bf600000,0x00000007bf74c010,0x00000007bfb00000) to space 5120K, 0% used [0x00000007bfb00000,0x00000007bfb00000,0x00000007c0000000) ParOldGen total 51200K, used 8K [0x00000007b9c00000, 0x00000007bce00000, 0x00000007bce00000) object space 51200K, 0% used [0x00000007b9c00000,0x00000007b9c02000,0x00000007bce00000) Metaspace used 4882K, capacity 5012K, committed 5248K, reserved 1056768K class space used 526K, capacity 564K, committed 640K, reserved 1048576K } 2020-03-01T16:39:57.916: Alloc 5MB
在执行第4行,分配5M内存时,Eden区不足,可是整个年轻代空余内存是大于5M的,因而触发了一次Young GC。
绝大多数收集器,都有这么一个策略:在执行Young GC以前,若是估计以前晋升老年代的平均大小,比当前老年代的剩余空间要大的话,则会放弃Young GC,转而触发Full GC。Parallel GC除了上述策略外,还有另一个策略:在执行Young GC以后,若是晋升老年代的平均大小,比当前老年代的剩余空间要大的话,则会触发一次Full GC。优化
public class TestApp { public static void main(String[] args) throws InterruptedException { byte[][] use = new byte[7][]; use[0] = allocM(10); use[1] = allocM(10); use[2] = allocM(10); use[3] = allocM(10); use[4] = allocM(10); use[5] = allocM(10); use[6] = allocM(10); Thread.sleep(1000); } private static byte[] allocM(int n) throws InterruptedException { byte[] ret = new byte[1024 * 1024 * n]; System.out.println(String.format("%s: Alloc %dMB", LocalDateTime.now().toString(), n)); Thread.sleep(500); return ret; } }
JVM参数为:
-Xms100m -Xmx100m -Xmn50m -XX:SurvivorRatio=8 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+UseParallelOldGC
,运行起来,打印日志以下(省略掉部分):
2020-03-01T16:02:43.172: Alloc 10MB 2020-03-01T16:02:43.693: Alloc 10MB 2020-03-01T16:02:44.206: Alloc 10MB {Heap before GC invocations=1 (full 0): PSYoungGen total 46080K, used 37274K [*, *, *) eden space 40960K, 91% used [*,*,*) from space 5120K, 0% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 51200K, used 0K [*, *, *) object space 51200K, 0% used [*,*,*) 2020-03-01T16:02:44.711-0800: [GC (Allocation Failure) [PSYoungGen: 37274K->1392K(46080K)] 37274K->32120K(97280K), 0.0163176 secs] [Times: user=0.09 sys=0.03, real=0.01 secs] Heap after GC invocations=1 (full 0): PSYoungGen total 46080K, used 1392K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 27% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 51200K, used 30728K [*, *, *) object space 51200K, 60% used [*,*,*) } {Heap before GC invocations=2 (full 1): PSYoungGen total 46080K, used 1392K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 27% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 51200K, used 30728K [*, *, *) object space 51200K, 60% used [*,*,*) 2020-03-01T16:02:44.728-0800: [Full GC (Ergonomics) [PSYoungGen: 1392K->0K(46080K)] [ParOldGen: 30728K->31945K(51200K)] 32120K->31945K(97280K), [Metaspace: 4881K->4881K(1056768K)], 0.0096352 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] Heap after GC invocations=2 (full 1): PSYoungGen total 46080K, used 0K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 0% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 51200K, used 31945K [*, *, *) object space 51200K, 62% used [*,*,*) } 2020-03-01T16:02:44.739: Alloc 10MB
执行完第一次Young GC
以后,因为年轻代的S区容量不足,因此Eden区中的30M内存会提早晋升到老年代。GC以后,老年代空间被占用了60%,还剩下40%(20M),而平均晋升内存大小为30M,因此触发悲观策略,致使了一次Full GC
。咱们将JVM中的
-Xms100m -Xmx100m
换成-Xms120m -Xmx120m
,从新执行日志以下:spa
2020-03-01T16:08:39.372: Alloc 10MB 2020-03-01T16:08:39.895: Alloc 10MB 2020-03-01T16:08:40.405: Alloc 10MB {Heap before GC invocations=1 (full 0): PSYoungGen total 46080K, used 37274K [*, *, *) eden space 40960K, 91% used [*,*,*) from space 5120K, 0% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 0K [*, *, *) object space 71680K, 0% used [*,*,*) 2020-03-01T16:08:40.906-0800: [GC (Allocation Failure) [PSYoungGen: 37274K->1360K(46080K)] 37274K->32088K(117760K), 0.0152322 secs] [Times: user=0.07 sys=0.03, real=0.02 secs] Heap after GC invocations=1 (full 0): PSYoungGen total 46080K, used 1360K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 26% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 30728K [*, *, *) object space 71680K, 42% used [*,*,*) } 2020-03-01T16:08:40.923: Alloc 10MB 2020-03-01T16:08:41.429: Alloc 10MB 2020-03-01T16:08:41.934: Alloc 10MB {Heap before GC invocations=2 (full 0): PSYoungGen total 46080K, used 32854K [*, *, *) eden space 40960K, 76% used [*,*,*) from space 5120K, 26% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 30728K [*, *, *) object space 71680K, 42% used [*,*,*) 2020-03-01T16:08:42.438-0800: [GC (Allocation Failure) [PSYoungGen: 32854K->1392K(46080K)] 63582K->62840K(117760K), 0.0151558 secs] [Times: user=0.07 sys=0.03, real=0.02 secs] Heap after GC invocations=2 (full 0): PSYoungGen total 46080K, used 1392K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 27% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 61448K [*, *, *) object space 71680K, 85% used [*,*,*) } {Heap before GC invocations=3 (full 1): PSYoungGen total 46080K, used 1392K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 27% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 61448K [*, *, *) object space 71680K, 85% used [*,*,*) Metaspace used 4883K, capacity 5012K, committed 5248K, reserved 1056768K class space used 526K, capacity 564K, committed 640K, reserved 1048576K 2020-03-01T16:08:42.454-0800: [Full GC (Ergonomics) [PSYoungGen: 1392K->0K(46080K)] [ParOldGen: 61448K->62634K(71680K)] 62840K->62634K(117760K), [Metaspace: 4883K->4883K(1056768K)], 0.0139615 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] Heap after GC invocations=3 (full 1): PSYoungGen total 46080K, used 0K [*, *, *) eden space 40960K, 0% used [*,*,*) from space 5120K, 0% used [*,*,*) to space 5120K, 0% used [*,*,*) ParOldGen total 71680K, used 62634K [*, *, *) object space 71680K, 87% used [*,*,*) } 2020-03-01T16:08:42.469: Alloc 10MB
能够看到,第一次Young GC
以后,因为老年代剩余空间足够大,并无触发Full GC,而随着内存继续分配,第二次Young GC
以后,仍是触发了悲观策略。
这个改进使得HotSpot VM在选择使用ParallelGC
(-XX:+UseParallelGC 或者是ergonomics自动选择)的时候,会默认开启-XX:+UseParallelOldGC
。这个变动应该是在JDK7u4开始的JDK7u系列与JDK8系列开始生效。
http://hg.openjdk.java.net/jd....net
--- a/src/share/vm/runtime/arguments.cpp Mon Jan 30 15:21:57 2012 +0100 +++ b/src/share/vm/runtime/arguments.cpp Thu Feb 02 16:05:17 2012 -0800 @@ -1400,10 +1400,11 @@ void Arguments::set_parallel_gc_flags() { assert(UseParallelGC || UseParallelOldGC, "Error"); - // If parallel old was requested, automatically enable parallel scavenge. - if (UseParallelOldGC && !UseParallelGC && FLAG_IS_DEFAULT(UseParallelGC)) { - FLAG_SET_DEFAULT(UseParallelGC, true); + // Enable ParallelOld unless it was explicitly disabled (cmd line or rc file). + if (FLAG_IS_DEFAULT(UseParallelOldGC)) { + FLAG_SET_DEFAULT(UseParallelOldGC, true); } + FLAG_SET_DEFAULT(UseParallelGC, true); // If no heap maximum was requested explicitly, use some reasonable fraction // of the physical memory, up to a maximum of 1GB.
在这个改变以前,即使选择了ParallelGC,默认状况下ParallelOldGC并不会随即开启,而是要本身经过 -XX:+UseParallelOldGC 去选定。在GC日志里,若是看到Full GC里有"ParOldGen"就是选择了ParallelOldGC。
[Full GC [PSYoungGen: 480K->0K(3584K)] [ParOldGen: 4660K->4909K(12288K)] 5141K->4909K(15872K) [PSPermGen: 11202K->11198K(22528K)], 0.0515530 secs] [Times: user=0.08 sys=0.00, real=0.05 secs]线程