JVM 并行垃圾回收器-PS PO

2,249 阅读6分钟

并行?并发?

并行 Parallel

并行是指 多个任务在同一时间运行

在垃圾回收器执行阶段中 我一般认为在同一时间多个垃圾回收线程同时在回收垃圾叫并行

并发 Concurrent

并发是指在同一阶段同时执行 比如单核CPU 在1秒钟之内执行了多个任务(CPU同一时间只能执行一个任务),那么这一秒钟的时间我们认为这些任务是并发执行,这里需要区分下并行意思是在同一时间多任务同时执行 多核CPU

在垃圾回收器中 我一般认为 GC线程和工作线程同时执行的叫并发垃圾回收器

PS - PO

PS PO是JDK1.8默认的垃圾回收器 处理是吞吐量优先的方式 使用的是分代模型

分代模型

image.png

吞吐量

吞吐量的计算方式是 程序执行时间 /(程序执行时间+GC执行时间)

响应时间

响应时间的方式就是 GC线程产生的STW越短 响应时间越高

Parallel Scavenge

Parallel Scavenge 作用于年轻代垃圾回收器 简称PS -XX:+UseParallelGC 采用并行执行的方式 使用PS的垃圾回收器 在服务模式Java8下默认采用PS的垃圾回收器 简单介绍PS垃圾回收器的处理流程

image.png

可以看出 在执行年轻代垃圾回收的时候 会触发一次STW停顿时间,这个时候并行回收器会使用多个线程一般等于CPU的核数处理垃圾

假设内存之前的分布为

image.png

执行PS回收时 会将 A A1 复制到S1区 如果C D分代年龄已够 那么会将CD移动至老年代

image.png

最后会清除Eden区所有数据

image.png

如果下次清理的时候新增了F G对象数据 那么执行后内存分布为

image.png

PS回收步骤:

  1. 新产生的对象会存在Eden区 经过一次回收后 会将Eden区存活的数据复制到 S1 区 年龄足够的 移动到老年代
  2. 第二次回收的时候 会将Eden区 存活的对象 和S1 区存活的对象复制到 S2区 清空 S1 和Eden 区数据,年龄足够的 移动到老年代
  3. 第三次回收的时候 会将Eden区 和 S2区存活的对象 复制到 S1区 将Eden区 和S2区清空 年龄足够的 移动到老年代
  4. .....依次类推

优点

采用的复制移动的算法实现 并且使用多线程并行的方式处理 速度效率非常快 并且 内存数据是连续的

缺点

因为采用的是复制算法,所以需要预留S1 S2内存空间,空间会形成浪费

调优方式 最低下统一说明

Paraller Old

Paraller Old 作用于老年代垃圾回收器 简称PO PO采用的是标记整理的方式 PS采用的是复制移动的方式

使用方式-XX:+UseParallelOldGC在Java8中默认的使用PO的垃圾回收器 处理老年代垃圾

假设老年代内存回收之前如下,蓝色的为有用数据

image.png

image.png

因为PO采用的是 标记整理的方式 所以 执行PO回收之后 内存数据为

image.png

优点

回收垃圾数据之后 内存连续 且不需要开辟额外的空间 不会造成内存浪费的情况

缺点

因为采用的 标记整理的算法,执行的效率偏慢因为需要涉及要 移动每一个对象且更改每个对象的指针数据,所以效率偏低

内存设定

采用PS+PO的方式 一般都设置 heap最大大小和初始大小 ,一般最大大小和初始大小都设置为一致,如果最大大小和初始大小不一致 则会产生动态收缩过程,此过程同样需要消耗CPU以及性能 并且GC还需要计算可用大小的各种比例去做动态收缩,具体大小设定的方式 通过测试预估一个合适的值,根据业务需求 服务器配置按需配置。

内存设定 并不是越大越好,越大的内存 是会减少GC的频率 但是 会导致单次GC STW的时间过长 最好的方式是根据实际测试的结果去做设定

通过–Xms、-Xmx 设置初始值 和 最大值

PS PO 调优

PS垃圾回收器因为使用了Copying的方式 所以调优一般着重于 分区大小 以及 吞吐量目标 和 停顿时间3个方向调优

分区大小

  1. 首先规划的是整个年轻代和老年代的比例 默认是 1:2 可以通过–XX:NewRatio 方式配置大小比例 或者通过 -Xmn 指定年轻代大小
  2. 设置Survivor区和Eden区大小比例 如果你的程序年轻代数据基本每一次都会被回收,那么可以适当的调小Survivor区大小 节省内存(一般不会去调节), 如果程序每次基本上都会有部分数据停留在年轻代,那么可以适当的调大Survivor区大小 通过参数 -XX:SurvivorRatio 默认为8 也就是 8:1:1 一般不太会调整 Survivor和Eden区的比例,除非你真的知道自己在做什么

吞吐量目标和STW

执行频率 需要根据业务场景而定 我们一般会避免发生太频繁的FGC和YGC 会根据停顿时间和执行频率做一个均衡

  1. 通过最大停顿时间做处理,根据JVM提供的-XX:MaxGCPauseMillis参数去设定你能接受的最大停顿时间,JVM会尽可能的在此时间内处理完垃圾回收(但是在某些情况下无法满足所需的暂停时间),参数默认值为0 如果设置了这个值比较小可能会导致频繁的GC也就是GC的执行频率会变高

  2. 通过-XX:GCTimeRatio 指定GC占用的实际为整个时间的比例, 例如-XX:GCTimeRatio=19将垃圾收集总时间的 1/20 或 5% 设定为目标,垃圾回收时间与应用程序时间之比为 1 / (1 + 对应值),垃圾回收所花费的时间是年轻代和老年代回收的总时间。如果未满足吞吐量目标,应该增加代的大小从而减少GC的频率

  3. -XX:+UseAdaptiveSizePolicy 这种模式下 年轻代 和 survivor区 和 晋升老年代的年龄都会自适应调整 以便达到吞吐量和响应时间的平衡

其他参数

增加并行线程数-XX:ParallelGCGThreads调整并行线程数,默认值为CPU的核数一般不用调整,等于CPU核数的时候会避免上下文切换 导致的时间消耗

修改晋升年龄 -XX:PretenureSizeThreshold 这里最大的年龄为15 因为每一个object的head上有4位代表晋升年龄 最大也就是15 默认值也为15