记一次服务器内存占用过高导致的简单JVM调优
继安装GPE分布式监控系统对服务器进行监控之后,陆续发现服务器内存占用率很高。有时间就对服务器上的应用进行逐一排查。
1.查看服务器状态
使用top命令看了下系统的状态:
top -c
看到系统的整体负载和cpu并不高,但是内存使用比较高(15.6/16),遂使用M进行内存占用排序
可以看到其中占用内存较大的应用均为java应用,而且应用数量有20+,每个java应用的启动参数设置的都比较大。导致服务器内存占用很高。
如何在保证应用正常使用的同时,减小应用占用内存,修改启动参数,成为了解决问题的关键。
2.优化-Xms,-Xmx参数
我们首先选用内存占用比例最高的PID:32477的java应用来进行分析,我们看到在启动参数中该应用配置的启动参数为
-Xms2048m -Xmx2048m
堆内存分配了2个G,那么怎么确定应用是否用到了这么大的堆内存呢?
第一步
我们jstat命令来查看一下JVM的GC情况,并通过GC信息来确定下一步优化的侧重点:
#即会每5秒一次显示进程号为32477的java进成的GC情况,一共显示5次
jstat -gc 32477 5000 5
结果如下图:
YGC平均耗时: 0.353s/9=0.8ms
FGC平均耗时:基本为0
平均大约40ms才会产生一次YGC
看起来似乎不错,YGC触发的频率不高,FGC的耗时也不高,但这样的内存设置是不是有些太浪费呢?(后续可以使用jmap工具强制触发FullGC来观察FullGC之后的老年代存活对象大小,使用命名jmap -dump:live,format=b,file=heap.bin <pid>
)
第二步
我们再使用jmap工具来查看堆内存使用情况
jmap -heap 32477
结果如下图:
可以看到老年代的内存占用仅为50M左右。那么根据老年代内存占用我们应该怎么分配堆内存设置呢,依据的原则是根据Java Performance里面的推荐公式来进行设置。
具体来讲:
Java整个堆大小设置,Xmx 和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍
永久代 PermSize和MaxPermSize设置为老年代存活对象的1.2-1.5倍。
年轻代Xmn的设置为老年代存活对象的1-1.5倍。
老年代的内存大小设置为老年代存活对象的2-3倍。
按照上述规则,按照整个堆大小是老年代(FullGC)之后的3-4倍计算的话,设置各代的内存应该情况如下:
Xmx=512m Xms=256m Xmn=64m PermSize=64m
老年代的大小为 (256-64=192m)为老年代存活对象大小的3倍左右
3.优化-XX:MetaspaceSize,-XX:MaxMetaspaceSize参数
MetaspaceSize含义
MetaspaceSize容量触发FGC的阈值。比如-XX:MetaspaceSize=256m,当MetaspaceSize容量超过256M时触发FGC,超过设定阈值后MetaspaceSize每扩容一次触发一次FGC;
第一步
我们jstat命令来查看一下JVM的Meta区使用率,并通过来Meta区使用率确定参数配置是否合理:
jstat -gcutil 32477
结果如下图:
第二步
然后,再通过jstat -gc 32477 5000 5
命令查看,结果如下图所示,计算MU/MC即Meta区的使用率确实达到了23.12%,但是MC,即Metaspace Capacity只有322304k,并不是参数MetaspaceSize指定的512m,结果如下图:
通过以上结果,我们可以发现MetaspaceSize容量设置的过大,导致Meta区使用率太低,浪费了比较多的堆内存。并取消了-XX:InitialBootClassLoaderMetaspaceSize参数的配置(或随着MetaspaceSize的内存分配减少而减少,普通项目配置为64M即可)。修改后的Meta参数配置为:
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m
InitialBootClassLoaderMetaspaceSize
64位下默认4M,32位下默认2200K,metasapce前面已经提到主要分了两大块,Klass Metaspace以及NoKlass Metaspace,而NoKlass Metaspace是由一块块内存组合起来的,这个参数决定了NoKlass Metaspace的第一个内存Block的大小,即2*InitialBootClassLoaderMetaspaceSize,同时为bootstrapClassLoader的第一块内存chunk分配了InitialBootClassLoaderMetaspaceSize的大小。
设置建议
MetaspaceSize和MaxMetaspaceSize设置一样大;
MetaspaceSize值建议设置为应用稳定运行后1.2-1.5倍,对于大部分项目256m即可.
4.输出GC日志,并使用GC工具查看应用GC情况
通过GC日志可视化分析工具,我们可以很方便的看到JVM各个分代的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量等,这些指标在我们进行JVM调优的时候是很有用的,这里我们选用GCeasy。
GCeasy介绍
官网地址:https://gceasy.io/,GCeasy是一款在线的GC日志分析器,可以通过GC日志分析进行内存泄露检测、GC暂停原因分析、JVM配置建议优化等功能,而且是可以免费使用的(有一些服务是收费的)。
第一步
设置GC日志输出参数,在应用启动时,加入对GC日志打印的语句。
GC参数
JVM的GC日志的主要参数包括如下几个:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2017-09-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:…/logs/gc.log 日志文件的输出路径
修改应用启动脚本,加入如下启动配置:
-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./gc.log
启动应用,在jar包的同级目录获得gc日志文件gc.log
第二步
将上述获得的日志文件导入第三方GC日志分析器:
JVM的各个分代区域分配的内存及使用峰值的内存
关键性能指标:吞吐量及GC暂停平均时间、最大时间、各个时间段的比例。
内存泄漏及fullGC异常
GC发生的原因、次数、时间等
4.修改启动参数后,对结果进行验证
经过上述修改,可以看到GPE上内存占用有比较明显的下降。
总结:
在内存相对紧张的情况下,可以按照上述的方式来进行内存的调优, 找到一个在GC频率和GC耗时上都可接受的一个内存设置,可以用较小的内存满足当前的服务需要
但当内存相对宽裕的时候,可以相对给服务多增加一点内存,可以减少GC的频率,GC的耗时相应会增加一些。 一般要求低延时的可以考虑多设置一点内存, 对延时要求不高的,可以按照上述方式设置较小内存。