笔记:Java 性能优化权威指南 第7章 JVM调优入门

一、调优工作流程

(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