上篇文章我们讲解了单线程垃圾收集器 Serial/SerialOld ,与之相对应的多线程垃圾收集器就是 Parallel Scavenge/Old, 本文我们讲解下多线程垃圾收集器 Parallel Scavenge/Old
垃圾收集器
- 新生代收集器: Serial、ParNew、Parallel Scavenge;
- 老年代收集器: Serial Old、CMS、Parallel Old;
- 通用收集器: G1;
收集器常用组合:
- Serial + Serial Old
- Parallel Scavenge + Parallel Old
- ParNew + CMS配合
- G1(不需要组合其他收集器)
今天我们要讲的就是 Parallel Scavenge/Old收集器,它采用了复制算法、并行回收、“Stop-the-World”机制,与ParNew对比
- Parallel Scavenge称为吞吐量优先的垃圾收集器,收集器的目标则是达到一个可控制的吞吐量
- 自适应调节策略也是Parallel Scavenge 与ParNew一个重要区别。
Parallel Old 收集器采用了标记-压缩算法,但同样也是基于并行回收和“Stop-the-World”机制、
1.Parallel Scavenge/Old Java8 默认垃圾收集器
查看你自己的jdk使用的默认垃圾收集器,如果你是java8,而且没有在项目中指定垃圾收集器,那么他就是ParallelGC 并行垃圾收集器
#查看jdk版本 使用的默认垃圾收集器使用的默认垃圾收集器
java -XX:+PrintCommandLineFlags -version
打印日志,XX:+UseParallelGC 默认是采用的多线程垃圾收集器
C:\Users\jzj>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=265507840 -XX:MaxHeapSize=4248125440 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)使用的默认垃圾收集器
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
2.JVM参数设置
-
-XX:+UserParallelGC :手动指定年轻代使用Parallel 并行收集器执行内存回收任务。
-
-XX:+UserParallelOldGC :手动指定老年代都是使用并行回收收集器。
- 分别适用于新生代和老年代。默认jdk8是开启的。
- 上面两个参数,默认开启一个,另一个也会被开启。二者相互激活
-
-XX:ParallelGCThreads 设置年轻代并行收集器的线程数
- 最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能。
- 默认情况下,当CPU数量小于8个,ParallelGCThreads的值等于CPU数量。
- 当CPU数量大于8个,ParallelGCThreads的值等于8 + (ncpus - 8 ) ( 5/8 )
- 在无特殊要求下,ParallelGCThreads参数使用默认值就可以了。
- 在JRE版本1.8.0_131之前,JVM无法感知Docker的CPU限制,会使用宿主机的逻辑核数计算默认值,如部署在128核物理机上的容器,JVM中默认ParallelGCThreads为83,远超过了容器的核数
- 导致过多的GC线程数抢占了业务线程的CPU时间,加上线程切换的开销,较大的降低了吞吐量
- 推荐升级1.8.0_192
-
-XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(即STW的时间)。单位是毫秒。
- 停顿时间越短体验越好,但是在服务器端,我们注重高并发,整体的吞吐量,所以服务器端适合Parallel,进行控制。
-
-XX:GCTimeRatio 垃圾收集时间占总时间的比例(=1/(N+1))
- 用于衡量吞吐量的大小。取值范围(0,100).默认值是99,也就是垃圾收集时间不超过1%.
-
-XX:+UserAdaptiveSizePolicy 设置Parallel Scavenge 收集器具有自适应调节策略。
- 在这种模式下,年轻代的大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整
- 自动调整从而达到堆大小,吞吐量和停顿时间的平衡点。
在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆大小,目标吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMills),让虚拟机自己完成调优工作。
3.测试GC日志
设置JVM参数,注意注意注意 我这里是没有设置使用何种GC回收方式的,采用的就是默认的 使用的默认垃圾收集器UseParallelGC
-verbose:gc -Xms10M -Xmx10M -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:SurvivorRatio=8
默认使用 UseParallelGC
- 年轻代 PSYoungGen
- 伊甸区 eden
- Surviral from区 from
- Surviral to区 to
- 老年代 ParOldGen
- 元空间 Metaspace
3.1 JVM测试类
Jvm测试类,设置JVM最大堆10M, for循环10次,每次增加1M的内存
package com.jzj.jvmtest.jvmready;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ParallelTest {
//JVM 参数 -verbose:gc -Xms10M -Xmx10M -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:SurvivorRatio=8
public static void main(String[] args) throws Exception {
byte[] b = null;
for (int i = 1; i <= 10; i++) {
//设置 1M的对象
log.info("======== " + i + "次添加1M对象");
b = new byte[1 * 1024 * 1024];
Thread.sleep(100);
}
}
}
打印GC日志
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->836K(9728K), 0.0008089 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2552K->512K(2560K)] 2884K->1202K(9728K), 0.0008076 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2541K->512K(2560K)] 3232K->1687K(9728K), 0.0009372 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
23:58:24.981 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 1次添加1M对象
23:58:25.090 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 2次添加1M对象
23:58:25.201 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 3次添加1M对象
23:58:25.312 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 4次添加1M对象
23:58:25.423 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 5次添加1M对象
23:58:25.531 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 6次添加1M对象
23:58:25.642 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 7次添加1M对象
[GC (Allocation Failure) [PSYoungGen: 2252K->496K(2560K)] 8547K->6976K(9728K), 0.0007743 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
23:58:25.751 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 8次添加1M对象
[GC (Allocation Failure) --[PSYoungGen: 1559K->1559K(2560K)] 8040K->8088K(9728K), 0.0009963 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1559K->0K(2560K)] [ParOldGen: 6528K->2581K(7168K)] 8088K->2581K(9728K), [Metaspace: 5077K->5077K(1056768K)], 0.0046449 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
23:58:25.860 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 9次添加1M对象
23:58:25.970 [main] INFO com.jzj.jvmtest.jvmready.ParallelTest - ======== 10次添加1M对象
Heap
PSYoungGen total 2560K, used 1506K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 73% used [0x00000000ffd00000,0x00000000ffe78990,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 4629K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 64% used [0x00000000ff600000,0x00000000ffa857f0,0x00000000ffd00000)
Metaspace used 5179K, capacity 5308K, committed 5504K, reserved 1056768K
class space used 574K, capacity 596K, committed 640K, reserved 1048576K
3.2 GC日志分析
GC日志参数 | 含义 |
---|---|
GC (Allocation Failure) | GC引起原因是年轻代没有足够空间存储新数据 |
Full GC (Ergonomics) | FullGC引起原因是老年代空间不足,gc垃圾回收后依旧存在大量对象 |
PSYoungGen | 新生代,这个名称由收集器决定。PS是Parallel Scavenge收集器的缩写 |
ParOldGen | Parallel Scavenge收集器配套的老年代 |
Metaspace | Parallel Scavenge收集器配套的永久代 |
total & used | 总的空间和用掉的空间 |
对GC日志进行分析
首先看下YoungGC
- GC (Allocation Failure) 内存分配失败,发生YoungGC
- PSYoungGen: 2048K->504K(2560K)] 2048K->844K(9728K), 0.0010931 secs 发生YGC,YGC前,新生代已使用2048K->gc发生后内存使用504K, 2560K是新生代区域总容量
- 换句话说就是 新生代回收前已使用 2048
- YGC回收后,新生代已使用504k
- YGC释放了多少内存,回收了多少垃圾? 2048-504 = 1544K的空间
- 新生代总大小为 2560K
- 2048K->836K(9728K) 表示YGC前 Java堆已使用容量2048K, YGC发生后Java堆使用了836K,Java的总的可用的堆容量为9728K
- 于Java堆来说 YGC释放了2048-836 = 1212K 的空间
- 0.0010931 secs YGC回收垃圾耗时
- Times: user=0.00 sys=0.00, real=0.00 secs
- 用户消耗cpu时间, 内核态消耗cpu时间, 真正操作消耗的时间
下面我们看下FullGC
Full GC (Ergonomics) 发生FullGC,清除所有区域的垃圾对象,发生了STW(Stop The World)
- PSYoungGen: 1551K->0K(2560K) 年轻代 gc前占用 1551K-> gc后 0K,年轻代对象全部被销毁,年轻代大小2560K
- ParOldGen: 6558K->2640K(7168K)] 老年代 gc前占用 6558K ->gc后 2640K,老年代也销毁了一部分,老年代大小7168K
- 8110K->2640K(9728K) Java堆大小 gc前占用 8110K ->gc后 2640K, 年轻代剩余0K+老年代剩余2640K 就是Java堆gc后的剩余 2640K, Java堆总大小 9782K
- Metaspace: 5084K->5084K(1056768K)], 0.0040923 secs] 元空间及该次FullGc消耗时间
这就是 ParallelGC 垃圾收集器的处理方式及GC的日志分析,我们可以通过GC日志看到新生代及老年代的存活对象情况,适当的调整参数,达到最完美的JVM状态