调整堆大小提高服务的吞吐量

修改tomcat jvm配置

初始配置

setenv.sh文件中写入(大小根据自己情况修改): setenv.sh内容如下:

1
2
3
4
5
6
7
8
export CATALINA_OPTS="$CATALINA_OPTS -Xms30m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:SurvivorRatio=8"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx30m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseParallelGC"
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"

优化配置

增大堆空间大小

1
2
3
4
5
6
7
8
export CATALINA_OPTS="$CATALINA_OPTS -Xms120m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:SurvivorRatio=8"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx120m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseParallelGC"
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"

JVM优化之JIT优化

逃逸分析后进行栈上分配、同步省略、标量替换。这些事默认开启的,具体参考jvm基础1

合理配置堆内存

推荐配置

在案例1中我们讲到了增加内存可以提高系统的性能而且效果显著,那么随之带来的一个问题就是,我们增加多少内存比较合适?如果内存过大,那么如果产生FullGC的时候,GC时间会相对比较长,如果内存较小,那么就会频繁的触发GC,在这种情况下,我们该如何合理的适配堆内存大小呢?

分析:
依据的原则是根据Java Performance里面的推荐公式来进行设置。

image-20211210112906534

Java整个堆大小设置,Xmx和Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。

方法区(永久代 PermSize和MaxPermSize或元空间MetaspaceSize和MaxMetaspaceSize)设置为老年代存活对象的1.2-1.5倍。

年轻代Xmn的设置为老年代存活对象的1-1.5倍。

老年代的内存大小设置为老年代存活对象的2-3倍。

但是,上面的说法也不是绝对的,也就是说这给的是一个参考值,根据多种调优之后得出的一个结论,大家可以根据这个值来设置一下我们的初始化内存,在保证程序正常运行的情况下,我们还要去查看GC的回收率,GC停顿耗时,内存里的实际数据来判断,Full GC是基本上不能有的,如果有就要做内存Dump分析,然后再去做一个合理的内存分配。

我们还要注意一点就是:上面说的老年代存活对象怎么去判定

如何计算老年代存活对象

查看日志

推荐/比较稳妥!

JVM参数中添加GC日志,GC日志中会记录每次FullGC之后各代的内存大小,观察老年代GC之后的空间大小。可观察一段时间内(比如2天)的FullGCL后的内存情况,根据多次的FullGC之后的老年代的空间大小数据来预估FullGC之后老年代的存活对象大小(可根据多次FullGC之后的内存大小取平均值)。

强制触发FULLGC

  • 会影响线上服务,慎用!
  • 查看的方式比较可行,但需要更改JVM参数,并分析日志。同时,在使用CMS回收器的时候,有可能不能触发FullGC,所以日志中并没有记录FullGC的日志。在分析的时候就比较难处理。所以,有时候需要强制触发一次FullGC,来观察FullGC之后的老年代存活对象大小。
  • 注:强制触发FullGC,会造成线上服务停顿(STW),要谨慎!建议的操作方式为,在强制FullGC前先把服务节点摘除,FullGC之后再将服务挂回可用节点,对外提供服务,在不同时间段触发FullGC,根据多次FullGC之后的老年代内存情况来预估FullGC之后的老年代存活对象大小
  • 如何强制触发Full GC?
    • jmap -dump:live,format=b,file=heap.bin <pid>将当前的存活对象dump到文件,此时会触发FullGc
    • jmap -histo:live <pid> 打印每个class的实例数目,内存占用,类全名信息..live子参数加上后,只统计活的对象数量.此时会触发FullGC
    • 在性能测试环境,可以通过Java监控工具来触发FullGC,比如使用VisualVM和JConsole,VisualVM集成了JConsole,VisualVM或者JConsole上面有一个触发GC的按钮。

案例:

现在我们通过idea启动springboot工程,我们将内存初始化为1024M。我们这里就从1024M的内存开始分析我们的 日志,根据我们上面的一些知识来进行一个合理的内存设置。

JVM设置如下:

1
2
3
4
5
6
7
8
9
10
-XX:+PrintGCDetails 
-XX:MetaspaceSize=64m
-Xss512K
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/heapdump3.hprof
-XX:SurvivorRatio=8
-XX:+PrintGCDateStamps
-Xms1024M
-Xmx1024M
-Xloggc:log/gc-oom3.log

你会估算gc频率吗

正常情况我们应该根据我们的系统来进行一个内存的估算,这个我们可以在测试环境进行测试,最开始可以将内存设置的大一些,比如4G这样,当然这也可以根据业务系统估算来的。

比如从数据库获取一条数据占用128个字节,需要获取1000条数据,那么一次读取到内存的大小就是(128 B/1024 Kb/1024M)* 1000 = 0.122M,那么我们程序可能需要并发读取,比如每秒读取100次,那么内存占用就是0.122100 = 12.2M,如果堆内存设置1个G,那么年轻代大小大约就是333M,那么333M80%/12.2M =21.84s ,也就是说我们的程序几乎每分钟进行两到三次youngGC。这样可以让我们对系统有一个大致的估算。

新生代与老年代比例

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

1
2
3
4
5
#开启:
-XX:+UseAdaptiveSizePolicy

#关闭
-XX:-UseAdaptiveSizePolicy

注意事项:
1、在 JDK 1.8中,如果使用CMS,无论 UseAdaptiveSizePolicy 如何设置,都会将UseAdaptiveSizePolicy设置为 false;不过不同版本的JDK存在差异;
2、UseAdaptiveSizePolicy不要和SurvivorRatio参数显示设置搭配使用,一起使用会导致参数失效;
3、由于UseAdaptiveSizePolicy会动态调整Eden、Survivor的大小,有些情

CPU占用很高排查方案

  • ps aux | grep java 查看到当前java进程使用cpu、内存、磁盘的情况获取使用量异常的进程
  • top -Hp 进程 pid 检查当前使用异常线程的pid
  • 把线程pid变为16进制如31695 - 》 7bcf然后得到0x7bcf
  • jstack 进程的pid | grep -A20 0x7bcf得到相关进程的代码

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

调整垃圾回收器提高服务的吞吐量

日均百万订单交易系统如何涉及jvm参数