1.什么是JVM性能优化
jvm性能优化涉及到两个很重要的概念:吞吐量和响应时间。jvm调优主要是针对他们进行调整优化,达到一个理想的目标,根据业务确定目标是吞吐量优先还是响应时间优先。
吞吐量:用户代码执行时间/(用户代码执行时间+GC执行时间)。
响应时间:整个接口的响应时间(用户代码执行时间+GC执行时间),STW时间越短,响应时间越短。
调优方法论
监控JVM性能
对JVM的运行情况进行监控,以了解应用程序的瓶颈和性能瓶颈
可以使用JVM自带的工具,如jstat、jmap、jstack等,或者第三方工具,如VisualVM、JProfiler等
压测基准指标
对程序进行压测,得出接口对应的吞吐量、响应时间等
外部现象
对用户体验来说,就是响应速度
可以用压测工具jmeter进行压测得出相关性能指标
内部现象:
分析GC情况,是JVM性能调优的重要因素,需要掌握GC的工作机制和GC日志的含义
可以使用JVM自带的GC日志或者第三方工具,如GCEasy等来分析GC情况,了解GC的频率、时间、内存占用等情况
调整JVM参数
通过调整堆大小、GC算法、线程池大小等参数来提高应用程序的性能
注意:不同的应用程序和环境可能需要不同的JVM参数配置,比如IO密集型和CPU密集型应用
二次压测分析
通过调整jvm参数后,二次压测看性能指标提升还是下降
内部:GC日志,看吞吐量,GC次数,停顿时间变化
外部:接口对应的吞吐量、响应时间是否更优
其他优化方式
优化代码
通过避免不必要的对象创建、减少同步操作、使用缓存等方式来优化代码。
注意:代码优化应该遵循“先正确,再优化”的原则,不应该牺牲代码的可读性和可维护性
使用并发编程
使用多线程、线程池等方式来提高并发性能,比如调整线程池的队列长度,存活线程数量等
注意:并发编程需要考虑线程安全和锁竞争等问题,需要进行正确的设计和实现
使用缓存
可以使用本地缓存、分布式缓存等方式来提高数据访问性能
注意:缓存需要考虑缓存一致性和缓存失效等问题,需要进行正确的设计和实现
避免IO阻塞
使用异步IO、NIO等方式来提高IO性能,比如前面学的CompletableFuture异步任务编排
注意:IO编程需要考虑并发性和可靠性等问题,需要进行正确的设计和实现
分布式+集群技术
使用负载均衡+集群技术,提升单节点的处理能力
2.JVM调优之压测环境准备
- SpringBoot 编写的jar的程序,接口一个返回随机组成的100个以内的对象的list (使用JDK17)
/** * @author lixiang * @date 2023/5/8 21:44 */ @Slf4j @RestController @RequestMapping("/spring-test") public class SpringTestController { @RequestMapping("query") public Map<String, Object> query() throws InterruptedException { int num = (int) (Math.random() * 100) + 1; //申请5MB内存 Byte[] bytes = new Byte[5 * 1024 * 1024]; List<Product> productList = new ArrayList<>(); for (int i = 0; i < num; i++) { Product product = new Product(); product.setPrice((int) Math.random() * 100); product.setTitle("商品编号" + i); productList.add(product); } Thread.sleep(5); Map<String, Object> map = new HashMap<>(16); map.put("data", productList); return map; } }
- Jmeter压测工具准备,测试计划 200并发,循环500次
3.JVM性能优化之堆大小配置
- 堆大小配置,FullGC次数的性能影响
- 性能优化初始值
-Xms1g # 配置初始堆内存1G -Xmx1g # 配置最大堆内存1G -XX:+UseG1GC # 使用G1回收器 -XX:MaxGCPauseMillis=200 # 设置最大停顿时间200ms -XX:G1HeapRegionSize=32M # 设置G1每个region块大小为32M -XX:ActiveProcessorCount=8 # 设置JVM使用的CPU核数限制为8 -XX:+HeapDumpOnOutOfMemoryError # 当JVM发生OOM时,自动生成DUMP文件 -XX:HeapDumpPath=heapdump.hprof # DUMP文件路径 -XX:+PrintCommandLineFlags # 监控开启 -Xlog:gc=info:file=portal_gc.log:utctime,level,tags:filecount=50,filesize=100M # Xlog:指定日志输出方式为日志文件。 # gc*:指定日志输出类型为GC相关的日志。 # info:指定输出日志的级别为info级别。 # file=portal_gc.log:指定日志输出的文件名为portal_gc.log。 # utctime:指定日志输出的时间戳使用UTC时间。 # level,tags:指定日志输出的格式包含级别和标签信息。 # filecount=50:指定最多保存50个日志文件。 # filesize=100M:指定每个日志文件的大小为100MB。
- 机器配置为:8核16G 500M带宽
- 设置初始堆内存和最大堆内存为1G,压测
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms1g -Xmx1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &
当我们设置堆内存为1G的时候,整体的吞吐量为40%以上,这已经很低了,期间Young GC发生了7451次,Full GC发生了142次
- 设置初始堆内存和最大堆内存为2G,压测
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &
当把堆内存设置为4G的时候,整体的吞吐量提升到76%,Young GC发生了504,一次Full GC都没有发生。
- 设置初始堆内存和最大堆内存为6G,压测
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms6g -Xmx6g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &
当把堆内存设置为6G的时候,整体吞吐量到达87%,Yong GC发生了196次,Full GC发生了0次。
总结:通过对堆内存的调整,发现4G是投入产出比最高的参数配置,所以当前配置可以采用4G的堆内存。
4.JVM性能优化之收集器配置
通过上面配置堆内存我们得出4G是当前机器和应用配置的最佳堆内存,这里我们不改变堆内存的大小,采用4G的堆内存,改变垃圾收集器,看看对接口吞吐量的影响。
这里我们采用ParallelGC,目前G1垃圾器在对于并发量大的应用来说,已经是最优的选择啦,我们这里用ParallelGC主要做一个对比。
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms4g -Xmx4g -XX:+UseParallelGC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &