Java JVM性能优化案例实操下篇

“这是我参与更文挑战的第11天,活动详情查看: 更文挑战

上篇介绍了Java JVM性能优化案例实操上篇,接下来介绍Java JVM性能优化案例实操下篇,在介绍前,先探讨下新生代与老年代的比例。

一、新生代与老年代的比例

Eden、S0、S1的比例真的是8:1:1吗?

参数设置

JVM 参数设置为:

# 打印日志详情          打印日志打印日期     初始化内存300M  最大内存300M   日志路径
-XX:+PrintGCDetails   -XX:+PrintGCDateStamps  -Xms300M  -Xmx300M -Xloggc:log/gc.log
复制代码

新生代 ( Young ) 与老年代 ( Old ) 的比例为 1:2,所以,内存分配应该是新生代100M,老年代 200M

我们可以先用命令查看一下堆内存分配是怎么样的:

# 查看进程ID
jps -l
# 查看对应的进程ID的堆内存分配
jmap -heap 14160
复制代码

我们可以看到堆内存配置的最大值MaxHeapSize = 300,新生代与老年代的比例NewRatio = 2,Eden、S0、S1的比例SurvivorRatio = 8,即 Eden:S0:S1 = 8:1:1
image.png

但实际Eden:S0:S1 = 8:1:1么?如下图所示

image.png

结果大家可以看到:我们的SurvivorRatio= 8 但是内存分配却不是8:1:1,这是为什么呢?
Eden:S0 = 75:12.5 = 6:1,是不是很神奇,莫急,听我慢慢道来!!!

参数AdaptiveSizePolicy

这是因为JDK 1.8 默认使用 UseParallelGC 垃圾回收器,该垃圾回收器默认启动了 AdaptiveSizePolicy,会根据GC的情况自动计算计算 Eden、From 和 To 区的大小;所以这是由于JDK1.8的自适应大小策略导致的,除此之外,我们下面观察GC日志发现有很多类似这样的FULLGC(Ergonomics),也是一样的原因。

我们可以在jvm参数中配置开启和关闭该配置:

  # 开启:
  -XX:+UseAdaptiveSizePolicy
  # 关闭
  -XX:-UseAdaptiveSizePolicy
复制代码

接下来我们关闭自适应进行测试是不是Eden:S0:S1 = 8:1:1了么?

image.png

如果不想动态调整内存大小,以下是解决方案:

  1. 保持使用 UseParallelGC,显式设置 -XX:SurvivorRatio=8。

image.png
2. 使用 CMS 垃圾回收器。CMS 默认关闭 AdaptiveSizePolicy。配置参数 -XX:+UseConcMarkSweepGC。

image.png

注意事项

  1. 在 JDK 1.8 中,如果使用 CMS,无论 UseAdaptiveSizePolicy 如何设置,都会将 UseAdaptiveSizePolicy 设置为 false;不过不同版本的JDK存在差异;
  2. UseAdaptiveSizePolicy不要和SurvivorRatio参数显示设置搭配使用,一起使用会导致参数失效;
  3. 由于UseAdaptiveSizePolicy会动态调整 Eden、Survivor 的大小,有些情况存在Survivor 被自动调为很小,比如十几MB甚至几MB的可能,这个时候YGC回收掉 Eden区后,还存活的对象进入Survivor 装不下,就会直接晋升到老年代,导致老年代占用空间逐渐增加,从而触发FULL GC,如果一次FULL GC的耗时很长(比如到达几百毫秒),那么在要求高响应的系统就是不可取的。
  4. 对于面向外部的大流量、低延迟系统,不建议启用此参数,建议关闭该参数。

二、CPU占用很高排查方案

案例

死锁问题,相信大家都知道,就不写案例了,自行脑补,说白了,就是互相等待对方手里持有的锁,我中有你,你中有我,僵持不下,干瞪眼!!!

问题分析

如果线程死锁,那么线程一直在占用CPU,这样就会导致CPU一直处于一个比较高的占用率。所示我们解决问题的思路应该是:

  1. 首先查看java进程ID
  2. 根据进程 ID 检查当前使用异常线程的pid
  3. 把线程pid变为16进制如 31695 -> 7bcf 然后得到0x7bcf
  4. jstack 进程的pid | grep -A20 0x7bcf 得到相关进程的代码

接下来是我们的实现上面逻辑的步骤,如下所示:

# 查看所有java进程 ID
jps -l
复制代码

结果如下:

image.png

根据进程 ID 检查当前使用异常线程的pid
top -Hp 1456
复制代码

结果如下:

image.png

从上图可以看出来,当前占用cpu比较高的线程 ID 是1465

接下来把 线程 PID 转换为16进制为

# 10 进制线程PId 转换为 16 进制
1465   ------->    5b9
# 5b9 在计算机中显示为   
0x5b9
复制代码

jstack 1465 | grep -A20 0x5b9

解决方案

  1. 调整锁的顺序,保持一致。
  2. 或者采用定时锁,一段时间后,如果还不能获取到锁就释放自身持有的所有锁。

三、G1并发执行的线程数对性能的影响

配置信息

硬件配置:8核

JVM参数设置

export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"
export CATALINA_OPTS="$CATALINA_OPTS -Xms30m"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx30m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDetails"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=64m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDateStamps"
export CATALINA_OPTS="$CATALINA_OPTS -Xloggc:/opt/tomcat8.5/logs/gc.log"
export CATALINA_OPTS="$CATALINA_OPTS -XX:ConcGCThreads=1"
复制代码

初始化内存和最大内存调整小一些,目的发生 FullGC,关注GC时间

关注点是:GC次数,GC时间,以及 Jmeter的平均响应时间

初始的状态

启动tomcat,查看进程默认的并发线程数:jinfo -flag ConcGCThreads pid

-XX:ConcGCThreads=1
没有配置的情况下:并发线程数是1

查看线程状态:jstat -gc pid

image.png

得出信息:

YGC:youngGC次数是1259次
FGC:Full GC次数是6次
GCT:GC总时间是5.556s
复制代码

Jmeter压测之后的GC状态:

image.png

得出信息:

YGC:youngGC次数是1600次
FGC:Full GC次数是18次
GCT:GC总时间是7.919s
复制代码

由此我们可以计算出来压测过程中,发生的GC次数和GC时间差

压测过程GC状态:

YGC:youngGC次数是 1600 - 1259 = 341次
FGC:Full GC次数是 18 - 6 = 12次
GCT:GC总时间是 7.919 - 5.556 = 2.363s
复制代码
Jmeter压测结果如下:
 
压测结果如下:
主要关注响应时间:
95%的请求响应时间为:16ms
99%的请求响应时间为:28ms
复制代码

image.png

优化之后

增加线程配置:
export CATALINA_OPTS=”$CATALINA_OPTS -XX:ConcGCThreads=8″

观察GC状态
jstat -gc pid

tomcat启动之后的初始化GC状态:

image.png

总结:
YGC:youngGC次数是 1134 次
FGC:Full GC次数是 5 次
GCT:GC总时间是 5.234s
复制代码

Jmeter压测之后的GC状态:

image.png

总结:
YGC:youngGC次数是 1347 次
FGC:Full GC次数是 16 次
GCT:GC总时间是 7.149s

由此我们可以计算出来压测过程中,发生的GC次数和GC时间差
 
压测过程GC状态:
YGC:youngGC次数是 1347 - 1134 = 213次
FGC:Full GC次数是 16 - 5 = 13次

GCT:GC总时间是 7.149 - 5.234 = 1.915s   提供了线程数,使得用户响应时间降低了。

压测结果如下:
主要关注响应时间:
95%的请求响应时间为:15ms
99%的请求响应时间为:22ms
复制代码

image.png

建议

配置完线程数之后,我们的请求的平均响应时间和GC时间都有一个明显的减少了,仅从效果上来看,我们这次的优化是有一定效果的。大家在工作中对于线上项目进行优化的时候,可以考虑到这方面的优化。

四、总结

本节开头介绍了新生代中Eden、S0、S1的比例,大家都知道是8:1:1,但默认不是这个比例,影响因素有垃圾回收器、自适应调整新生代堆内存参数UseAdativeSizePolicy、SurvivorRatio、都有关系。其次介绍了CPU占用很高排查方案、G1并发执行的线程数对性能的影响的案例,希望能对大家有所帮助!

欢迎大家关注公众号(MarkZoe)互相学习、互相交流。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享