一、工具篇
要对我们的系统进行调优,首先需要知道我们的系统目前是什么样子的,存在什么问题,jvm内存是什么样子,设定的jvm参数是什么样子,用的垃圾收集器是什么,只有知道这些信息,才能进行下一步调优,所以,调优第一步,首先查看目前系统的情况,下面是几个常用的工具
1、jmp命令
命令: jmap -histo 7800(进程号,根据jps查看当前系统进程号)>./log.txt (将内存信息输入到当前目录下txt文件)
文件如图:
从这里面我们可以知道,在我们的系统中,哪些对象实例是最多的,占的内存最大,后面我们进行调优很有可能就是基于这些对象的特点进行调优
2、内存溢出生成当时快照
‐XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath=D:\jvm.dump
这个命令是我们系统的黑匣子(对比飞机上的黑瞎子),它的作用是在系统oom发生得那一刻,记录当时系统得内存信息,对象信息,新生代老年代分配情况等等一系列信息,并输出到指定文件,我们依靠dump文件导入到一些可视化工具,进而分析什么原因导致得oom。
3、Jstat命令
jstat -gc pid 最常用,可以评估程序内存使用及GC压力整体情况
如图
如图
jstat -gc pid 1000 10 (输出10次,每次间隔1秒),这个可以很清晰得看到新生代,老年代,元空间的大小以及使用情况,能让我们更好的的分析GC整体压力以及性能瓶颈
4、Jinfo 查看系统参数命令
jinfo -flags 进程id (查看运行中都jvm系统参数)
jinfo -sysprops 进程id 查看java系统参数
5、Arthas
这个是阿里开源的一个非常好用的在线诊断工具 ,他的功能非常强大,我们常用的命令有:
(1) dashboard 可以查看整个进程的运行情况,线程、内存、GC、运行环境信息
(2) thread 可以查看线程详细情况
(3) thread加上线程ID 可以查看线程堆栈
(4) thread -b 可以查看线程死锁
(5) jad加类的全名 可以反编译,这样可以方便我们查看线上代码是否是正确的版本
这里我们只是简单的罗列了一些常用的命令 在线文档 :https://arthas.aliyun.com/doc/ 具体我们可以参考文档使用,其实笔者所在公司,arthas作为基础镜像打入到了我们docker镜像构建中,所以我们每个容器都会自带arthas,很方便对容器进行在线调试诊断,有兴趣的同学不妨试试。
二、应用篇
当有了工具之后,我们就可以对系统进行评估,首先我们需要了解我们的服务器配置,以及设定的java内存大小,还有知道我们的系统采用的是什么来及收集器以及相关的参数配置,已笔者所在公司为例,大部分系统都是用的cms垃圾收集器,这里我们回顾一下垃圾收集的整体过程
CMS整体流程图
几个阶段:
1、初始标记:暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快。
2、并发标记 基于初始标记的对象,继续向下延展标记,应用线程不会停,过程比较长,由于应用线程不停,已经标记的对象状态可能发生改变
3、重新标记 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,比初始标记长,但远远没有并发标记时间长。应用线此阶段暂停
4、并发清理 基于之前的标记清理垃圾对象 ,应用线程不暂停
5、并发重置 清理对象的标记 ,应用线程不暂停
同手我们还要了解所用垃圾收集器的核心参数设定,因为,我们调优很有可能涉及到垃圾器的调优,主要就是对垃圾收集器的核心参数修改已cms为例
1、-XX:+UseConcMarkSweepGC:启用cms
2、-XX:ConcGCThreads:并发的GC线程数
3、-XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
4、.-XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
5、-XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
6、-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一 次
7、-XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
那么我们熟悉了系统的内存信息以及垃圾收集器之后还需要哪些信息?
JVM运行情况预估
1、用 jstat gc -pid 命令可以计算出如下一些关键数据,有了这些数据就可以采用之前介绍过的优化思路,先给自己的系统设置一些初始性的JVM参数,比如堆内存大小,年轻代大小,Eden和Survivor的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值等。
2、年轻代对象增长的速率 可以执行命令 jstat -gc pid 1000 10 (每隔1秒执行1次命令,共执行10次),通过观察EU(eden区的使用)来估算每秒eden大概新增多少对象,如果系统负载不高,可以把频率1秒换成1分钟,甚至10分钟来观察整体情况。注意,一般系统可能有高峰期和日常期,所以需要在不同的时间分别估算不同情况下对象增长速率。
3、Young GC的触发频率和每次耗时 知道年轻代对象增长速率我们就能推根据eden区的大小推算出Young GC大概多久触发一次,Young GC的平均耗时可以通过 YGCT/YGC公式算出,根据结果我们大概就能知道系统大概多久会因为Young GC的执行而卡顿多久。
4、每次Young GC后有多少对象存活和进入老年代 这个因为之前已经大概知道Young GC的频率,假设是每5分钟一次,那么可以执行命令 jstat -gc pid 300000 10 ,观察每次结果eden,survivor和老年代使用的变化情况,在每次gc后eden区使用一般会大幅减少,survivor和老年代都有可能增长,这些增长的对象就是每次Young GC后存活的对象,同时还可以看出每次Young GC后进去老年代大概多少对象,从而可以推算出老年代对象增长速率。
5、Full GC的触发频率和每次耗时 知道了老年代对象的增长速率就可以推算出Full GC的触发频率了,Full GC的每次耗时可以用公式 FGCT/FGC 计算得出。
优化思路其实简单来说就是尽量让每次Young GC后的存活对象小于Survivor区域的50%,都留存在年轻代里。尽量别让对象进入老年代。尽量减少Full GC的频率,避免频繁Full GC对JVM性能的影响。
触发的full gc的几个条件
1、老年代空间不足:新生代如果容量不足会将对象放到老年代,老年代空间不足是会触发full gc,通过-Xmn设置新生代大小,也可以通过-XX:NewRatio=n设置比例,默认值是2,老年代:新生代=》2:1
2、永久代空间不足:永久代在jdk7是存放在堆中的,可以通过-XX:PermSize=n和XX:MaxPermSize=n设置大。在jdk8中叫做元空间,使用的是计算机的本地内存通过-XX:MaxMetaspaceSize=n设置大小,如果不设置,默认最大内存大小是计算机的本地内存,由于运行时常量池在方法区中,而永久代又是方法区的实现,所以运行时常量池随着jdk8也移动到了本地内存,但是无论是jdk7还是jdk8,字符串常量池还是在堆中,字符串常量的创建是需要消耗堆内存的
3、CMS 产生碎片过多已经扛不住压力了就会调用full gc进行整合:可以通过-XX:CMSFullGCsBeforeCompaction=n设置要执行多少次full GC才会做压缩。默认是0,也就是每次full gc时就会对空间碎片进行整理,在默认配置下如果碎片过多CMS GC顶不住了,就要转入full GC的时候都会做压缩。(如果Full GC比较频繁,那么就不能每次都整理内存空间,不然积少成多,停顿的时间也是很可观的,此时就要调大该参数,让CMS在经过多次Full GC后再对内存空间进行压缩整理,而如果Full GC发生的不频繁,间隔时间较长,就可以设置成每次Full GC后都会对内存空间进行压缩整理,影响也不大。)
4、CMS GC时出现了promotion failed和concurrent mode failure:
(1)promotion failed意思是晋升失败是由于新生代把一些对象往老年代扔,然后老年代空间不足则抛出“promotion failed”,触发full gc,可能的原因是:Survivor空间过小或者老年代空间小或者碎片多,或者两者同时发生2、concurrent mode failure是CMS设置启动的老年代内存占比阈值过高,所以导致系统无法预留足够的空间满足程序需求,就会出现concurrent mode failure,启动担保机制,
5、老年代增长过快触发full gc进行清理,解决方法是降低触发CMS的阀值,使用-XX:CMSInitiatingOccupancyFraction调低阈值,默认值是68,可以调到50
6、统计得到新生代minor gc时晋升到老年代的平均大小大于老生代剩余空间:
原因有几个:1、代码问题大量大对象直接进入老年代 2、老年代空间不足,通过-XX:NewRatio=n可以调整老年代和年轻的堆比例
7、代码直接调用System.gc()会建议系统调用full gc:在GC日志中会显示为[Full GC(System),可以开启-XX:-DisableExplicitGC禁止此类full gc
三、对比篇
正常来说,对jvm的优化,并不是立杆见影的,甚至一次调整并没有效果,一般来说,如果我们的系统是分布式集群环境,同一系统有多台服务的话可以现在一两台机器上做调优设定,基于调整过的服务容器运行情况和未调整过的容器情况做的对比分析。通过一段时间的对比分析来确定有没有效果。