JVM系列文章目录
JVM调优之内存优化与GC优化
前言
本文基于JDK1.8,主要介绍堆内存大小调整堆JVM效率堆影响。
Apache Bench
我在此次调优中使用该压测工具进行测试,这里详细安装操作就不进行介绍了(我这边是因为mac自带测试方便再加上这个东西也比较清,当然各位也可以用其他测试工具,如jmeter),各位可以参考官网。
内存调优与GC调优
测试工程
我这里简单的构造一个spring boot 工程,只有简单的对象构建存储,这样就可以抛除代码性能问题等一系列其他干扰因素,只关心内存相关信息。
工程结构:
测试接口代码:
package cn.abfeathers.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @author: abfeathers
* @date: 2021/3/20
* @description: 测试接口
*/
@RestController
@RequestMapping("/jvm")
public class TestController {
@RequestMapping("/heap")
public String test(){
List<Byte[]> list = new ArrayList<Byte[]>();
Byte[] b = new Byte[1024*1024];
list.add(b);
return "success";
}
}
JVM默认内存分配下进行压测
本来想再做一组1000并发和10000并发的测试的,mac 自带的ab有一些bug,高并发没办法测试,本来准备新装一个ab,然后替换原来的,但是M1改不了权限,就能拿这个200并发的意思一下了。我这边强制将堆空间压缩到了200M -Xms200m -Xmx200m
-
启动测试工程。
-
先进行一轮测试预热。
我们采用10的并发,测试10万请求。ab -c 10 -n 10000 -k http://localhost:8080/jvm/heap
查看GC情况jstat -gc 1613 5000 20 | awk '{print $13,$14,$15,$16,$17}'
-
200并发,测试10万请求。
测试结束,GC情况:
测试结果:
- 平均吞吐量大约在:503/s。
- JVM平均处理请求时间:1.986ms。
- JVM发生GC次数:1996次YGC,耗时11.331s;FGC次数253,耗时5.988s,总GC耗时17.32s。
优化一
这次我将JVM内存增大到500M -Xms500m -Xmx500m
,再来进行100并发,10万请求的压测。
-
重新启动启动测试工程。
-
再次采用10的并发,测试10万请求进行预热。
-
200并发,测试10万请求。
- 平均吞吐量大约在:1772/s。
- JVM平均处理请求时间:0.564ms。
- JVM发生GC次数:519次YGC,耗时3.494s;FGC次数21,耗时0.476s,总GC耗时4.113s。
优化二
这次我不光将JVM内存增大到500M,还调整堆空间各个分区的比例8:1:1,扩大Eden区 -Xms500m -Xmx500m -XX:SurvivorRatio=8
,再来进行100并发,10万请求的压测。
-
重新启动启动测试工程。
-
再次采用10的并发,测试10万请求进行预热。
-
200并发,测试10万请求。
- 平均吞吐量大约在:2463/s。
- JVM平均处理请求时间:0.406ms。
- JVM发生GC次数:313次YGC,耗时2.126s;FGC次数22,耗时0.430s,总GC耗时2.555s。
调优总结
需要考虑一下四个方面
-
GC 频率
高频的 FullGC 会给系统带来非常大的性能消耗,虽然 MinorGC 相对 FullGC 来说好了许多,但过多的 MinorGC 仍会给系统带来压力。 -
内存
这里的内存指的是堆内存大小,堆内存又分为年轻代内存和老年代内存。堆内存不足,会增加 MinorGC ,影响系统性能。 -
吞吐量
频繁的 GC 将会引起线程的上下文切换,增加系统的性能开销,从而影响每次处理的线程请求,最终导致系统的吞吐量下降。 -
延时
JVM 的 GC 持续时间也会影响到每次请求的响应时
其实我们在一般项目中,堆空间大小都不太大,而当高并发的时候实际上是需要一个大堆空间,但是光调大堆空间效果还不是最好的,我们还要合理的分配Eden区、From区、To区的大小,因为在高并发的场景下,大部分对象都是“一次性”用品,完全不需要回收。
而且一味的调大堆空间有时候也反而会使性能降低,因为单次 Minor GC 时间是由两部分组成:T1(扫描新生代)和 T2(复制存活对象)。当对象存活时间>MinorGC间隔时间的时候,而新生代内存比例又没有调整的话,就有可能降低性能。
推荐策略
-
新生代大小选择
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,新生代收集发生的频率也是最小的。同时,减少到达老年代的对象。
- 吞吐量优先的应用:尽可能的设置大,可能到达 Gbit 的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合 8CPU 以上的应用.。
- 避免设置过小。当新生代设置过小时会导致:1.MinorGC 次数更加频繁 2.可能导致 MinorGC 对象直接进入老年代,如果此时老年代满了,会触 发 FullGC.
-
老年代大小选择
- 响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可 以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式; 如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息、持久代并发收集次数、传统 GC 信息、花在新生代和老年代回收上的时间比例。 - 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而 老年代尽存放长期存活对象。
- 响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可 以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式; 如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得:
下一篇:JVM调优之预估调优与问题排查