JVM调优-JVM调优实践二

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情


1.写在前面

在上一篇文章中,我们分享了调优的主要步骤,和gc日志的分析。

分享了一个gc日志分析的可视化,在线分析工具,(gceasy.io/)

具体的详情可以查看:JVM调优-JVM调优实践一

那我们今天就继续往下进行分析:

  • 堆内存与元空间优化
  • 堆内存内部优化:新生代和老年代比例
  • 线程堆栈优化

那就废话不多说了,直接上正菜吧:

2.堆内存与元空间优化

2.1 监控分析

JVM内存占用情况:

image.png

MetaSpace空间浪费严重,有 3Full GC
复制代码

GC统计:

image.png

2.2 判断

image.png

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中测试:

image.png

-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可视化

image.png

  • gc统计

image.png

  • gc原因

image.png

收集GC日志,发现没有因为扩容而频繁GC,GC的时间0.027秒,已经小于 1 秒,并且频率不高。

已经达到调优目标,应用到所有服务器配置。

结论:

  1. 问题:Metaspace空间够用:没有再发生Full GC
  2. 问题:用很少量的内存,扛下来很高频并发1000TPS,4GB --> 800MB
  3. 问题:GC时间超过了 1 秒
  4. 问题: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够用 了,大的应用建议使用1k。

注意:这个选项对性能影响较大,需要严格的测试确定最终大小。

优化后的参数配置:

JAVA_OPT="${JAVA_OPT} -Xms836m -Xmx836m -XX:MetaspaceSize=128m -Xss512k"

JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-default.log"
复制代码

idea设置:

image.png

回扣,JVM优化就是一个发现问题解决问题过程。如果没有问题并不需要优化。

过度优化本身就是问题

Xss128k内存占用:直接就是启动报错

Xss512k内存占用:

image.png

结论:

设置为-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内存划分

image.png

4.1.2.关键性能指标:

image.png

4.1.3. 交互可视化图形

image.png

4.1.4. GC统计

image.png

由上可以看到,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内存划分

image.png

4.2.2. 关键性能指标:

image.png

4.2.3. 交互可视化图形

image.png

4.2.4. GC统计

image.png

由上,可见,3:1,效果更不好了,出现了full gc

结论:采用默认的比例即可:1:2


由此可见,我们这里,已经是对堆内存与元空间优化,堆内存内部优化;新生代和老年代比例;线程堆栈优化。

由于jvm调优实践的分析,篇幅比较长,所以今天就先到这里,剩下的留着下次分享了。


好了,以上就是JVM调优实践二的分享了。

个人理解,可能也不够全面,班门弄斧了。

如果觉得有收获的,帮忙点赞、评论、收藏一下呗!!!

image.png

猜你喜欢

转载自juejin.im/post/7128641152877608973