Java-Parallel GC介绍

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

收集器

=======

Parallel Scavenge

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

新生代并行回收器,内存分布使用的复制算法。 Parallel Scavenge 主要关注的是应用的吞吐量,而其他收集器关注的主要是尽可能的缩短STW(stop the word)的时间。

吞度量=t1/(t1+t2)

t1运行用户代码的总时间

t2运行垃圾收集的总时间

比如,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

Parallel Scavenge 收集器提供了两个参数来用于精确控制吞吐量,一是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数,二是控制吞吐量大小的 -XX:GCTimeRatio 参数

  • -XX:MaxGCPauseMillis

参数的值是一个大于0的毫秒数,收集器将尽可能的保证回收耗费的时间不超过设定的值,但是,并不是越小越好,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的,如果设置的值太小,将会导致频繁GC,这样虽然GC停顿时间下来了,但是吞吐量也下来了。比如收集500MB时候,需要每10秒收集一次,每次回收耗时100ms;如果收集300MB的时候,需要每5秒收集一次,每次回收耗时70ms,虽然每次回收耗时更少,但是工作频次提高,导致吞吐量反而降低了。

  • -XX:GCTimeRatio

参数的值是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,默认值是99,就是允许最大1%(即1/(1+99))的垃圾收集时间。

Parallel Scavenge有个重要的特性,是支持GC自适应的调节策略,使用-XX:UseAdaptiveSizePolicy参数开启,开启之后,虚拟机会根据当前系统运行情况收集监控信息,动态调整新生代的比例、老年大大小等细节参数,以提供最合适的停顿时间或最大的吞吐量。开启这个参数之后,就不需要再设置新生代大小,Eden与S0/S1的比例等等参数。

Parallel Old

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

Java-Parallel GC介绍

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

悲观策略

========

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

}

最后

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
在这里插入图片描述

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
,,)

from space 5120K, 26% used [,,*)

to space 5120K, 0% used [,,*)

ParOldGen total 71680K, used 30728K [*, *, *)

object space 71680K, 42% used [,,*)

}

最后

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
[外链图片转存中…(img-OoPrNVgp-1714671947386)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值