JVM调优-JVM调优实践二
1.写在前面
在上一篇文章中,我们分享了调优的主要步骤,和gc日志的分析。
分享了一个gc日志分析的可视化,在线分析工具,(gceasy.io/)
具体的详情可以查看:JVM调优-JVM调优实践一
那我们今天就继续往下进行分析:
- 堆内存与元空间优化
- 堆内存内部优化:新生代和老年代比例
- 线程堆栈优化
那就废话不多说了,直接上正菜吧:
2.堆内存与元空间优化
2.1 监控分析
JVM内存占用情况:
MetaSpace空间浪费严重,有 3 次Full GC
GC统计:
2.2 判断
GC主要原因扩容,扩容时间为1.65秒大于 1 秒,故需要进行调优。
2.3 确定目标
则其他堆空间的分配,基于以下规则来进行。
老年代的空间大小为106MB
- 堆内存:参数-Xms和-Xmx,建议扩大至3-4倍FullGC后的老年代空间占用。
- 106 * (3-4) = (318-424)MB ,设置heap大小为424MB;
- 经过线上一段时间,GC日志中老年代空间占用
- 元空间:参数-XX:MetaspaceSize=N,设置元空间大小为128MB;
- 新生代:参数-Xmn,建议扩大至1-1.5倍FullGC之后的老年代空间占用。106M*(1-1.5)=(209-159)M,设置新生代大小为159MB;
- 不设置:新生代和老年代比例:1 : 2
# 调整参数,基于当前系统运行情况这是最佳配置
JAVA_OPT="${JAVA_OPT} -Xms424m -Xmx424m -Xmn159m -XX:MetaspaceSize=128m"
# -Xms堆最小内存
# -Xmx堆最大内存
# -Xmn新生代大小:
# -XX:MetaspaceSize元空间大小
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-best-heap-
metaspace.log"
idea中测试:
-Xms424m -Xmx424m -Xmn159m -XX:MetaspaceSize=128m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-default.log
重新启动项目,对gc日志文件,使用gceasy分析。
2.4 对比差异
- gc可视化
- gc统计
- gc原因
收集GC日志,发现没有因为扩容而频繁GC,GC的时间0.027秒,已经小于 1 秒,并且频率不高。
已经达到调优目标,应用到所有服务器配置。
结论:
- 问题:Metaspace空间够用:没有再发生Full GC
- 问题:用很少量的内存,扛下来很高频并发1000TPS,4GB --> 800MB
- 问题:GC时间超过了 1 秒
- 问题:Metaspace存在无效空间占用
由此可见,对于堆内存与元空间的优化,基本上达到了调优目标。
那接下来,我们再对其他方面进行调优:线程堆栈
3.线程堆栈优化-让线程数实现翻倍
花小钱办大事,调优的目的是使用更小的硬件承载更大的吞吐量。
这里先回顾一下,线程堆栈大小,怎么设置(可能大部分人都忘了)?
-Xss每个线程的栈内存:默认1M,一般来说是不需要改的
这里要如何设置-Xss的值呢?这里有一个公式:
# 最大的线程数的影响因素:
Max Of Thread = (机器本身可用内存 -(JVM分配的堆内存+JVM元数据区)) / Xss值
机器环境:
指标 | 参数 |
---|---|
机器 | CPU 12核,内存16GB |
集群规模 | 单机 |
seqb_web版本 | 1.0 |
数据库 | 4核 16G |
由上面的公式,我们计算一下:
- 机器本身可用内存:机器内存16G
- JVM分配的堆内存:424m (上面堆内存调优的值)
- JVM元数据区:128m (上面堆内存调优的值)
# 开启一个线程,需要占用的内存1m
(16384 - (424+128)) / 1m = 15832个
(16384 - (424+128)) / 0.5m = 31664个线程数
(16384 - (424+128)) / 0.25m = 63328个线程数
# 理论大小上线:现在可以不加16GB内存,也让当前值理论最大线程数翻倍
#配置大小的时候可以怎么写?
Xss512k
Xss512m
Xss512g
# 注意:你的服务端线程数能够做到1w
对于不同版本的Java虚拟机和不同的操作系统,栈容量最小值可能会有所限制,这主要取决于操作系统内存分页大小。
譬如上述方法中的参数-Xss128k可以正常用于 32 位Windows系统下的JDK 6,但是如果 用于 64 位Windows系统下的JDK 11,则会提示栈容量最小不能低于180K,而在Linux下这个值则可能是228K,如果低于这个最小限制,HotSpot虚拟器启动时会给出如下提示:
The Java thread stack size specified is too small. Specify at least 228k
那么问题来了,Xss应该设置多少呢?
JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为 256k 。
更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。
但是操作系统对一个进程内的线程数还是有限制的,不能无限生成, 1w~2w左右 一般小的应用, 如果栈不是很深, 应该是 512k够用 了,大的应用建议使用1m。
注意:这个选项对性能影响较大,需要严格的测试确定最终大小。
优化后的参数配置:
JAVA_OPT="${JAVA_OPT} -Xms424m -Xmx424m -Xmn159m -XX:MetaspaceSize=128m -Xss512k"
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-default.log"
idea设置:
回扣,JVM优化就是一个发现问题解决问题过程。如果没有问题并不需要优化。
过度优化本身就是问题
Xss128k内存占用:直接就是启动报错
Xss512k内存占用:
结论:
设置为-Xss512k,线程数直接翻倍。(达到进一步优化)
那接下来,我们再对其他方面进行调优:新生代和老年代比例
4.堆内存内部优化:新生代和老年代比例
参数-Xmn可以设置为1-1.5倍FullGC之后的老年代空间占用 ,一般采用默认比例。
-Xmn设置了新生代大小,设置完成新生代和老年代比例: 1:2
避免新生代设置过小,当新生代设置过小时,会产生两种比较明显的现象,一是minor GC次数频繁,二是可能导致 minor GC对象直接进入老年代。当老年代内存不足时,会触发Full GC。
避免新生代设置过大,当新生代设置过大时,会带来两个问题:一是老年大变小,可能导致FullGC频繁执行;二是 minor GC 执行回收的时间大幅度增加。
年轻代和老年代大小默认比例: 1 : 2
4.1 比例1:8
- -XX:NewRetio = 4 表示young和old所占比值为1:4
JAVA_OPT="${JAVA_OPT} -Xms424m -Xmx424m -XX:MetaspaceSize=128m -Xss512k -XX:NewRatio=8"
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-young-vs-old8.log"
年轻代空间更小了,触发young gc 次数更多了,因此young gc消耗大量的gc时间,对应性能来说是有所影响的;
4.1.1. JVM内存划分
4.1.2.关键性能指标:
4.1.3. 交互可视化图形
4.1.4. GC统计
由上可以看到,young gc次数变多了,这也有点影响性能。
结论:不可取!!!
4.2 比例3:1【约】
优化参数设置:
JAVA_OPT="${JAVA_OPT} -Xms424m -Xmx424m -XX:MetaspaceSize=128m -Xss512k -Xmn318m"
这里说一下,这个318是怎么计算的呢?
318 = 109 * 3
这里 106就是上面得到的:老年代的空间大小为106MB
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -
XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-young-vs-old3.log"
4.2.1. JVM内存划分
4.2.2. 关键性能指标:
4.2.3. 交互可视化图形
4.2.4. GC统计
由上,可见,3:1,效果更不好了,出现了full gc
结论:采用默认的比例即可:1:2
由此可见,我们这里,已经是对堆内存与元空间优化,堆内存内部优化;新生代和老年代比例;线程堆栈优化。
由于jvm调优实践的分析,篇幅比较长,所以今天就先到这里,剩下的留着下次分享了。
好了,以上就是JVM调优实践二的分享了。
个人理解,可能也不够全面,班门弄斧了。
如果觉得有收获的,帮忙点赞、评论、收藏
一下呗!!!
转载自:https://juejin.cn/post/7128641152877608973