配置为:
- jdk1.8
- 服务器:4核8G
开篇:本篇仅以博主观念来讲解JVM一般会用到的参数,不做线上实例的讲解(因为展开太多了(╬▔皿▔)凸)。
首先上来就是一套稍微全一点的jvm参数配置(建议一般3G的堆大小即可,可根据实际情况调整(考虑到系统还有其他会用到内存、系统自己也会用内存)):
-Xms3072M -Xmx3072M -Xmn2048M
-Xss1M
-XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=15
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/home/logs/xxx/gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/logs/xxx // 这个目录路径可不加,若不加默认在tomcat目录下输出java_<pid>_<date>_<time>_heapDump.hprof
有了上面这套参数,那我们怎么配?
1 springboot应用配置方式(红色部分是jvm启动参数):
-Xms128m -Xmx256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/home/logs/hll-uc-service/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/logs/hll-uc-service
2 tomcat应用配置方式:
需要编辑
# vim /home/tomcat/crm/bin/catalina.sh
-------------------------------------修改JAVA_OPTS如下--------------------------------------------------
JAVA_OPTS='-Xms32m -Xmx256m
-Xss1M
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=15
-XX:PretenureSizeThreshold=1M
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/home/logs/crm/gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/logs/crm
..................................................'
-------------------------------------修改JAVA_OPTS如上--------------------------------------------------
这么多参数,是不是看着蒙蔽,下来一个个讲解下(还附带其他参数~)
参数 | 描述 |
-Xms3072M -Xmx3072M | 设置堆大小为3G(含新生代+老年代),默认4C8G的机器用这个配置可以,具体要看系统可适量提高为4G |
-Xmn2048M | 设置堆中新生代2G,3-2=1G,说明老年代只剩1G |
-Xss1M | 设置栈大小 |
-XX:SurvivorRatio=8 | 设置新生代比例 = Eden : S1 : S2 = 8 : 1: 1 |
-XX:MaxTenuringThreshold=15 | 设置分代年龄为15 |
-XX:PretenureSizeThreshold=1M | 设置多大的对象可以直接进入老年代(这里是1M),默认值0,不管多大都先进eden。这个要合理设置,不过弄得老年代对象太多 |
-XX:+UseParNewGC | 设置新生代使用ParNew垃圾回收器 |
-XX:+UseConcMarkSweepGC | 设置老年代使用CMS垃圾回收器 |
-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 | 设置每多少次Full GC,老年代就触发一次整理,我这里设置为0表示触发一次就清除一次,好处是碎片得到整理,坏处是老年代GC变长。如果设置为10就10次触发一次整理,这样老年代碎片很多,很容易就触发Full GC |
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/home/logs/xxx/gc.log | 设置GC日志打印,每次的新生代、老年代、元空间回收都会记录在gc.log,便于分析 |
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/logs/xxx | 设置应用堆溢出的时候生成一个堆溢出快照,然后可以用MAT来进行分析 |
-XX:-+DisableExplicitGC | 禁用System.gc();,因为调用该函数会触发full GC,但是有不少开源项目会使用Sysem.gc(),比如Netty,所以这个最好不开启,手动规范不乱使用System.gc()即可 |
-XX:CMSInitiatingOccupancyFaction=92 | 设置老年代空间占用到多大时触发Full GC,可不设置,默认就是92% |
参数之间配合有什么效果?为什么要这样调(这里粗略过,展开讲就是另一篇文章)?
1 先举一个日活过亿的电商网站来讲解(先看完这个再继续看后面)
2 总结
从上面其实很简单看出GC的优化是什么?再根据实际场景中来尽量减少Full GC,仅触发young GC,因为young GC虽然也会造成停顿,但是停顿的时间很短,几ms~几十ms之间,所以几乎不对系统进行影响。但要达到这样的目的怎么做了?
很简单!尽量不触发old gc,可从如下几方面入手:
- 调大新生代比例,比如3G堆中给他2G新生代带下
- 调大Eden与S区的比例,因为假设一次存活对象太多了,然后放另一个清空好的S2区放不下,就会把垃圾放到老年代,所以需要调大比例
- 开启空间担保机制,这个从jdk1.6后就默认不用开启,所以参数说明我没列出来(假设jdk版本太低没开这个, 新生代 > 老年代剩余空间就会触发full gc,这样太坑了)
怎么观测上面调整后的参数效果?
1 java自带命令(如jstat),详情看如下文章(jstat -gc pid 5 5)
2 日志入手(如gc.log)
个人觉得可以不用这些的,用linux命令查看统计就够了(比如这样的) --> grep "PSYoungGen" gc.log | wc -l
GC时间嫌长?系统内存过大GC过久?实时性要求高?用G1试试!
1 G1相对于parNew + CMS有什么优势
G1是用于新生代老年代,最大的优点是最大预测停顿时间,G1是根据垃圾大概回收哪些时间最短来回收的,并且可设置为了达到这个目标需要回收多少次,默认八次。适用于大内存的应用,比如几十G,你新生代GC一次都不短时间了,所以这时候用G1最好。或者是要求是实时性高的都可以用G1