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 小时前
多进程和多线程的特点和区别
java·开发语言·jvm
Engineer邓祥浩9 小时前
JVM学习笔记(6) 第二部分 自动内存管理 第5章节 调优案例分析与实战
jvm·笔记·学习
墨神谕11 小时前
解释执行与JIT
jvm
滑德友13 小时前
jvm的metaSpace内存溢出问题排查
jvm
sinat_2554878114 小时前
泛型:类·学习笔记
java·jvm·笔记·学习
闻哥14 小时前
Docker Swarm 负载均衡深度解析:VIP vs DNSRR 模式详解
java·运维·jvm·docker·容器·负载均衡
yangyanping201081 天前
Go语言学习之对象关系映射GORM
jvm·学习·golang
Barkamin1 天前
多线程简单介绍
java·开发语言·jvm
「QT(C++)开发工程师」1 天前
C++17三大实用特性详解:内联变量、std::optional、std::variant
jvm·c++
她说..1 天前
Java Object类与String相关高频面试题
java·开发语言·jvm·spring boot·java-ee