一、调优工作流程
(1)、划分系统需求优先级(内存消耗、响应时间、吞吐量、可用性、可管理性、启动时间等)
(2)、选择JVM部署模式:单JVM还是多JVM,32位或64位,64位指针压缩
(3)、选择JVM Runtime:-client 还是 server 模式,垃圾回收器Parallel 还是 CMS
(4)、调整内存使用
(5)、调整时间延迟
(6)、调整吞吐量
二、应用程序的系统需求
1、可用性:例子:即使某个组件发生了不可预料的失效,也不会导致整个程序无法使用。
2、可管理性:例子:由于人力资源有限,应该部署尽量少的JVM数量。
3、吞吐量:例子:程序每秒应该完成2500次事务。
4、响应延时:例子:程序应该在60毫秒内完成请求的处理工作。
5、内存占用:例子:程序在8G内存的系统上单例方式运行,或者24G内存系统上3个实例运行。
6、启动时间:例子:应用程序初始化在15秒以内。
三、对系统需求进行分级
比如互联网、分布式,对响应延时要求高。
四、选择JVM部署模式
单JVM:管理方便,但是存在单点故障。
多JVM:高可用性、低延时,监控、管理、维护更难。
五、选择JVM 运行模式
Client模式:启动快、占用内存少、JIT编码器生成代码快。
Server模式:提供了更复杂的生成代码优化功能。一般服务器使用Server模式。
Linux中,如内存小于2G:使用32位模式
Linux中,如内存在2G~32G:使用64位模式,并启用指针压缩,-d64 -XX:+UseCompressedOops
Linux中,如内存大于32G:使用64位模式,不启用指针压缩,-d64
一般情况下,Parallel 收集器能达到应用程序停顿要求,若再需要低延时,则转向CMS收集器。
六、垃圾收集调优基础
主要调节三个属性:吞吐量、延时、内存占用。一般提高某一个属性都会消耗另外两个属性。
调解的三个原则:
(1)、Minor GC 应该尽可能多收集垃圾,尽量减少Full GC。
(2)、Java Heap空间越大,垃圾收集效果越好,吞吐量和延时越好
(3)、三个属性中选择两个进行调优 (GC 调优3选2原则)
调优应该开启GC 日志:-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XXloggc:/opt/jvm-gc.log
-XX:PrintGCDateStamps 打印日历
-XX:PrintGCApplicationConcurrentTime 打印两个安全点之间应用程序运行的时间,判定应用程序是否在执行
-XX:PrintGCApplicationStoppedTime 打印由于安全点操作而阻塞的时间,确认延迟是来自安全点还是其他问题
-XX:PrintSatePointStastics 打印安全点统计信息
七、确定内存占用
1、JVM布局
(1)、关注吞吐量、低延时的应用应该把 -Xmx 和 -Xms 设为同一个值,避免扩展或缩减新生代或老生代空间而进行的Full GC。
(2)、使用-Xmn 将新生代的初始值和最大值设为同一值。
(3)、关注性能的应用应该将 -XX:PermSize 和 -XX:MaxPermSize 设为同一值,因为永久代大小调整需要Full GC。
(4)、如果老生代或永久代内存不足,不管其他代内存是否充足,都会发生Full GC。
2、Heap 大小调优步骤
(1)、先使用JVM默认配置、或者先指定-Xmx和-Xms,将程序推进到稳定状态中,等程序出现了老年代、永久代的内存溢出,则增加老生代大小 (-Xms 和 -Xmx ),增加永久代大小(-XX:PermSize 和 -XX:MaxPermSize),如此迭代,直到不再出现内存溢出,程序稳定。
(2)、计算活跃数据大小:即程序运行与稳定状态后,老年代、永久带分别占用Java Heap的大小。可使用 VisualVM 触发Full GC,看日志。
(3)、配置堆空间初始堆大小:
-Xmx 和 -Xms : Heap总容量为 老生代活跃数据大小的3~4 倍
-Xmn: 新生代容量为 老生代活跃数据大小的1~1.5倍,即老生代容量为老生代活跃数据的2~3倍
-XX:PermSize -XX:MaxPermSize : 永久代活跃数据大小的1.5倍
八、确定调优延迟/响应性
1、从日志中统计以下数据
Minor GC的持续时间、Minor GC的频率、Full GC的最长时间、Full GC的最大频率
2、优化新生代大小
若Minor GC 时间过长,减少新生代空间。
若Minor GC 过于频繁,增大新生代空间。
例子:
假设应用程序的延迟性要求是40毫秒, Minor GC 频率为54毫秒,超过了要求,所以应该减少内存。
假设应用程序的频率要求是5秒一次, Minor GC 频率为2.147秒一次,超过了要求,所以应该增大内存。
注意:
(1)、调节-Xmn 的时候需要同时调解 -Xms 和 -Xmx, 保证老年代大小不变。
(2)、老生代容量至少为老生代活跃数据的1.5倍
(3)、新生代大小至少为Heap大小的10% (过小会导致Minor GC 频繁)
(5)、增大Java Heap大小时候,不可以超过JVM 可用的物理内存数 (使用虚拟内存性能底下)
3、优化老生代大小
Full GC 的频率过高,则增大老年代大小,增加老年代大小的同时应该保持新生代大小不变。
Full GC 的延时过长,增大老年代一般无效,需要切换使用CMS 调优器,-XX:UseConcMarkSweepGC,并继续调优
可以查看Minor GC 的提升率,预测Full GC 的频率:
Minor GC 的日志可以查看 Heap 的总容量: totalHeap
查看新生代的总容量: totalYoung
查看MInor GC'的时间间隔: minorTime
计算老生代的容量:totalOld = totalHeap - totalYoung
在Full GC的日志中可以查看老生代的活跃数据大小:activeOld
计算老生代的空闲空间:freeOld =totalOld - activeOld
Minor GC 中观察每次新生代的剩余空间 freeYoung1、freeYoung2、freeYoung3
Minor GC 中观察每次Heap的剩余空间 freeTotal1、freeTotal2、freeTotal3、freeTotal4
于是可以求得每次MinorGC后提升到老年代占用空间:occupancyOld1= freeTotal1- freeYoung1, occupancyOld2= freeTotal2- freeYoung2,......
求得每次Minor GC提升到老生代的数据量:promoteOld1 = occupancyOld2-occupancyOld1; promoteOld2 = occupancyOld3-occupancyOld2
求得每次Minor GC提升到老生代数据量的平均值 promoteOld
老生代空间占满需要的次数:promoteTotalCount = activeOld /promoteOld;
老生代空间占满需要的时间:promoteTotalTime = promoteCount * minorTime
promoteTotalTime 即可推测出Full GC 多久发生一次。
4、为CMS调优延迟
采用CMS的绝对最差延迟比Parallel的最差延迟更长,所以,从Parallel转换到CMS的时候,需要将老年代空间增大20%-30%.
处理CMS的老年代碎片问题:减少对象从新生代提升到老年代的比率。
5、调优Survivor空间
Survivor过小会将很多对象提升到老生代,引发Full GC。
(1)、通过命令 -XX:+PrintTenuringDistribution 可以观察Survivor空间对象的年龄组成。
(2)、调优Survivor与Eden的比例:-XX:SurvivorRatio
每个Survivor容量 = 新生代容量 / (SurvivorRadio +2) ;
通过 -XX:PrintTenuringDistribution 可以观察所有对象年龄的总大小,统计Survivor空间存活的对象总大小,将Survivor大小设为存活对象大小的2倍 (默认 -XX: TargetSurvivorRatio=50 )。
增大Survivor的同时,应该保持Eden区不变,即需要同时增大-Xmn,同时还要老年代大小不变,故而还需要增大-Xmx 和 -Xms
(3)、调优对象提升到老年代的年龄:-XX:MaxTenuringThreshold
一般使用默认值 -XX:MaxTenuringThreshold =15 。通常情况下,宁可对象在Survivor之间多次复制,也不要将其匆匆复制到老年代。
可以 -XX:PrintTenuringDistribution 监控
6、调优CMS发生时刻 -XX:CMSInitiatingOccupancyFraction=<percent> -XX:CMSInitialingOccupancyOnly
CMSInitiatingOccupancyFraction 过大,则CMS 启动太晚,则执行时间长,甚至内存溢出失败。
CMSInitiatingOccupancyFraction 过小,则CMS 启动太早,则执行频繁,几乎回收不到垃圾。
CMSInitiatingOccupancyFraction 起码应该大于 老年代活跃对象大小/老年代总大小,否则将陷入死循环。
CMSInitiatingOccupancyFraction 一般大于 1.5倍的 老年代活跃对象大小/老年代总大小。作为初始值,参照日志继续调优。
最好同时设置 -XX:CMSInitialingOccupancyOnly ,使得JVM 一直使用这个percent,否则JVM会在使用了第一遍之后又会转向自适应。
7、禁止 显示垃圾收集
-XX:+DisableExplicitGC 禁止System.gc().
8、调优永久代垃圾收集
CMS 默认不开启垃圾回收,开启需要设置:-XX:CMSClassUnloadingEnabled
同时配置使用:-XX:CMSPermGenSweepingEnabled -XX:CMSInitiatingPermOccupanyFraction=<percent> -XX:UseCMSInitiatingOccupancyOnly
9、调优CMS停顿时间
初始标记是单线程的,极少占用时间。
重新标记是多线程的,占用较多时间,可以调节线程数:+XX:ParallelGCThreads=<n>,默认值 = 8+ (处理器核数-8)*5/8 ,约为 3 + 处理器数*0.6 。可以调小这个值,减少停顿。
强制重新标记之前进行Minor GC: -XX:CMSScavengeBeforeRemark
减少大量引用对象、可终结对象带来的垃圾收集持续时间:-XX:+ParallelRefProcEnabled.
九、调优吞吐量
1、CMS的吞吐量调优
增大新生代,减少Minor GC的次数
增大老生代,减少CMS次数
优化Survivor,减少提升到老生代对象
优化CMS启动条件,晚点启动。
一般CMS 包括Minor GC所带来的开销应该小于10%, 可以优化减少到1%-3%。
2、Parallel的吞吐量调优
禁用自适应:-XX:-UseAdaptiveSizePolicy
调优Survivor空间:
调优并行垃圾收集器:
在NUMA系统部署:使用 -XX:+UseNUMA
十、其他性能命令
1、开启实验性的优化 (有风险,可能不稳定)
-XX:+AggressiveOpts
2、逃逸分析优化
-XX:+DoEscapeAnalysis
JVM会采用以下方式优化:对象展开、标量替换、栈上分配、消除同步、消除垃圾收集的读写屏障
3、偏向锁
-XX:+UseBiasedLocjing
4、大页面支持
-XX:+UseLargePages