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

相关推荐
阿伟*rui10 分钟前
jvm入门
jvm
学点东西吧.3 小时前
JVM(五、垃圾回收器)
jvm
请你打开电视看看6 小时前
Jvm知识点
jvm
程序猿进阶6 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
阿龟在奔跑18 小时前
引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例
java·jvm·安全·list
王佑辉18 小时前
【jvm】方法区常用参数有哪些
jvm
王佑辉18 小时前
【jvm】HotSpot中方法区的演进
jvm
Domain-zhuo19 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
Theodore_10222 天前
7 设计模式原则之合成复用原则
java·开发语言·jvm·设计模式·java-ee·合成复用原则
我是苏苏2 天前
Web开发:ORM框架之使用Freesql的DbFrist封装常见功能
java·前端·jvm