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 停顿次数和停顿时间都降低了。

相关推荐
yuanbenshidiaos13 小时前
c++---------数据类型
java·jvm·c++
java1234_小锋16 小时前
JVM对象分配内存如何保证线程安全?
jvm
40岁的系统架构师19 小时前
1 JVM JDK JRE之间的区别以及使用字节码的好处
java·jvm·python
寻找沙漠的人19 小时前
理解JVM
java·jvm·java-ee
我叫啥都行19 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
bufanjun0011 天前
JUC并发工具---ThreadLocal
java·jvm·面试·并发·并发基础
东阳马生架构1 天前
JVM简介—1.Java内存区域
jvm
工程师老罗1 天前
Android笔试面试题AI答之SQLite(2)
android·jvm·sqlite
Qzer_4072 天前
jvm字节码中方法的结构
jvm
奇偶变不变2 天前
RTOS之事件集
java·linux·jvm·单片机·算法