JVM 内存分配的调优过程

以一个创建 1MB 对象的接口来模拟万级并发请求产生大量对象的场景。

java 复制代码
 @RequestMapping(value = "/test1")
 public String test1(HttpServletRequest request) {
   List<Byte[]> temp = new ArrayList<Byte[]>();
   
   Byte[] b = new Byte[1024*1024];
   temp.add(b);
   
   return "success";
 }

AB 压测

shell 复制代码
ab -n <请求数> -c <并发数> 'http://127.0.0.1:8080/test1'

分别对应用服务进行压力测试,以下是请求接口的吞吐量和响应时间在不同并发用户数下的变化情况:

并发请求数 响应时间(Time per request) 吞吐量(Requests per second)
10/10000 5.971 1674.74
30/10000 20.105 1551.20
50/10000 31.154 1602.10
70/10000 57.319 1185.61
90/10000 70.585 1278.02
100/10000 96.512 1040.35
120/10000 109.021 1095.57

总体来看,当并发数量到了一定值时,吞吐量就上不去了,响应时间也迅速增加。

分析 GC 日志

此时我们可以通过查看具体的 GC 日志,设置 VM 配置参数,将运行期间的 GC 日志 dump 下来,具体配置参数如下:

cpp 复制代码
 -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:heapTest.log

收集到 GC 日志后,使用 GCViewer 工具打开,查看到具体的 GC 日志如下:

主页面显示 FullGC 发生了 8 次,右图显示年轻代和老年代的内存使用率几乎达到了 100%。

而 FullGC 会导致 stop-the-world 的发生,从而严重影响到应用服务的性能。

此时,我们需要调整堆内存的大小来减少 FullGC 的发生。

参考指标

GC 频率

高频的 FullGC 会给系统带来非常大的性能消耗,虽然 MinorGC 相对 FullGC 来说好了许多,但过多的 MinorGC 仍会给系统带来压力。

内存

此内存指的是堆内存大小,堆内存又分为年轻代内存老年代内存

首先我们要分析堆内存大小是否合适,其实是分析年轻代和老年代的比例是否合适。

如果内存不足或分配不均匀,会增加 FullGC,严重的将导致 CPU 持续爆满,影响系统性能。

吞吐量

频繁的 FullGC 将会引起线程的上下文切换,增加系统的性能开销,从而影响每次处理的线程请求,最终导致系统的吞吐量下降。

延时

JVM 的 GC 持续时间也会影响到每次请求的响应时间。

具体调优方法

调整堆内存空间减少 FullGC:通过日志分析,堆内存基本被用完了,而且存在大量 FullGC,这意味着堆内存严重不足,需要调大堆内存空间。

cpp 复制代码
-Xms3g -Xmx3g

调大堆内存之后,再来测试下在 100 并发下的性能情况,发现吞吐量提高了 40% 左右,响应时间也降低了将近 50%。

再查看 GC 日志,发现 GC 频率降低了,老年代的使用率只有 2% 了。

调整年轻代减少 MinorGC:通过调整堆内存大小,我们已经提升了整体的吞吐量,降低了响应时间。

还可以将年轻代设置得大一些,从而减少一些 MinorGC。

cpp 复制代码
-Xms3g -Xmx3g -Xmn2g

查看 GC 日志,发现 MinorGC次数 明显降低了,GC 花费的总时间也减少了。

设置 Eden、Survivor 区比例: 在 JVM 中,如果开启 AdaptiveSizePolicy,则每次 GC 后都会重新计算 EdenFrom SurvivorTo Survivor 区的大小,计算依据是 GC 过程中统计的 GC 时间、吞吐量、内存占用量,这个时候 SurvivorRatio 默认设置的比例会失效。

在 JDK1.8 中,默认是开启 AdaptiveSizePolicy 的,可以通过 -XX:-UseAdaptiveSizePolicy 关闭该项配置,或显示运行 -XX:SurvivorRatio=8 将 Eden、Survivor 的比例设置为 8:2。

大部分新对象都是在 Eden 区创建的,我们可以固定 Eden 区的占用比例,来调优 JVM 的内存分配性能。


通过上图可以看到关闭AdaptiveSizePolicy后,吞吐量提升了,响应时间降低了,GC 停顿次数和停顿时间都降低了。

相关推荐
麦兜*1 小时前
Spring Boot启动优化7板斧(延迟初始化、组件扫描精准打击、JVM参数调优):砍掉70%启动时间的魔鬼实践
java·jvm·spring boot·后端·spring·spring cloud·系统架构
真实的菜12 小时前
JVM类加载系统详解:深入理解Java类的生命周期
java·开发语言·jvm
在未来等你13 小时前
JVM调优实战 Day 15:云原生环境下的JVM配置
java·jvm·性能优化·虚拟机·调优
黄雪超1 天前
JVM——函数式语法糖:如何使用Function、Stream来编写函数式程序?
java·开发语言·jvm
ThetaarSofVenice1 天前
对象的finalization机制Test
java·开发语言·jvm
很小心的小新1 天前
12、jvm运行期优化
java·开发语言·jvm·笔记
ThetaarSofVenice1 天前
垃圾收集相关算法Test
java·jvm·算法
暮 夏1 天前
Java测试题一
java·开发语言·jvm
程序员弘羽2 天前
C++ 第四阶段 内存管理 - 第二节:避免内存泄漏的技巧
java·jvm·c++
好名字更能让你们记住我2 天前
Linux多线程(十二)之【生产者消费者模型】
linux·运维·服务器·jvm·windows·centos