写点什么

Java-Parallel GC 介绍,springmvc 面试题高级

  • 2021 年 11 月 10 日
  • 本文字数:5797 字

    阅读完需:约 19 分钟

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



Parallel Old GC?在?Parallel Scavenge?和?Parallel Old?收集器组合中,负责 Full GC,是一个并行收集器,其在整理年轻代的时候,使用与 Parallel Scavenge GC 一样的常规“复制”算法,但是在整理老年代的时候,是使用的基于“标记-整理”算法优化的“Mark–Summary-Compaction”算法。


算法包含三个部分


  • Mark


首先将老年代的内存,划分为大小固定的多个连续 Region,当标记完存活对象之后,统计每个 Region 的存活对象数量。Mark 阶段采用串行标记所有从 GC Roots 可直达的对象,然后并行标记所有存活的对象。


  • Summary


某个 Region 的密度 = 存活对象的内存大小 / Region 内存大小。因为每次整理会将存活的对象向 Old 区的左侧移动,而对象存活越久,理论上就越不容易被回收,所以经过多次整理之后,左侧 Region 中的对象更偏向于稳定、“长寿”,即是左侧 Region 的密度更大。Summary 阶段,算法采用以空间换时间的优化方式,针对一个密度很大的 Region,比如 95%的空间是存活对象,只有断断续续 5%的空间是未使用的,那么算法认为这个 Region 不值得被整理,即是选择浪费掉这 5%的空间,以节省整理操作的时间开销。在 Sumamry 阶段,首先从左至右计算各个 Region 的密度,直到找到一个 point,这个 point 左侧的 Region 都不值得整理,右侧的 Region 需要整理。point 左侧的 Region 被称为 dense prefix,这个区域内的对象都不会被移动。Summary 阶段是一个串行执行的阶段。


  • Compaction


Compaction 阶段利用 Summary 阶段的统计数据,针对需要整理的部分,采用“整理”算法进行并行操作。


推荐观看:Java架构300集


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


GC 策略


========


  • -XX:+ScavengeBeforeFullGC


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 有点变化:


  • 当整个新生代剩余的空间无法存放某个对象时,Parallel GC 中该对象会直接进入老年代;

  • 而如果整个新生代剩余的空间可以存放但只是 Eden 区空间不足,则会尝试一次 Minor 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);?,重新执行,得到日志如下:


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 spac


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


e 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。


悲观策略


========


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?,重新执行日志如下:


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 [,,*)

评论

发布
暂无评论
Java-Parallel GC介绍,springmvc面试题高级