《Java性能优化权威指南》学习笔记
编译
编译是指生成机器码 即 二进制目标文件的过程。
Java最初是转换为类文件,虚拟机将其转为字节码。
运行时动态的转为机器码。
JIT会在运行时,将调用次数超过阈值CompileThreshold的代码编译(由方法调用计数器计数)。
JVM性能监控
重要的垃圾收集数据
堆大小。
新生代、老年代、永久代大小
Minor GC /Full GC 的持续时间、频率、空间回收量
打印垃圾收集信息 -XX+PrintGCDetails
可以启动时开启, 也可用jinfo 动态开启
常用GC启动参数
推荐 -XX:+PrintGCDetails -XX:+PrintGCDateStamps-Xloggc:<filename> -XX:+UseConcMarkSweepGC -Xmx400m -Xms400m -Xmn30m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:G:/学习/gclog.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=G:/学习/dump.hprof
资料:https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
-XX:PrintGCTimeStamps 打印gc的时候打印时间戳
-XX:+UseConcMarkSweepGC 使用cms垃圾回收器
-Xmn30m 新生代30M 默认 SurvivorRatio 8, eden:s0:s1为8:1:1,所以新生代为9,即30m*0.9=27m
MetaspaceSize 为本地内存。 非堆。
-Xmx400m -Xms400m 最大堆内存 400M,最小堆内存400M, 老年代=400m-30m=370m
jmap -head jpsid
收集的时候打印时间戳
-XX:+PrintGCTImeStamps 启动秒数
-XX:+PrintGCDateStamps 时间戳
堆
内存分布
图片来源于网络
Memory Pools
新生代
Minor GC
GC后 Eden几乎是空的。
大多数新对象分配在Eden,大对象可能直接分配到老年代。(-XX:PretenureSizeThreshold
)
MonorailGC后 ,两个Survivor交换角色。
“足够老”的存活对象提升到老年代。
用到复制算法。
MinorGC过程中,Survivor(to)可能不足以容纳Eden+另一个Survivor(from)中的存活对象。此时就需要将Surviv偶然中的存活对象溢出?多余的对象转移到老年代–过早提升(Premature Promotion)。 — 如果老年代满了,无法容纳更大对象。(提升失败),minor GC后会导致Full GC.
Minor GC:Eden+to 存活对象–> to 不够? ----> 老年代 不够? ----> Full GC
老年代
“足够老”的存活对象提升到老年代。
永久代
类加载 ,将类的源数据信息加载到永久代(jdk8以下)
永久代满,引发垃圾收集-Full GC.
当需要加载其他类而空间不足,未使用的类就会从永久代中被卸载。
永久代垃圾回收不是stop-the-world
相关参数: -XX:PermSize 、 -XX:MaxPerSize
垃圾回收器
分代垃圾收集器
基于以下观察事实:
- 大多数分配对象朝生夕死。
- 存活时间久的对象很少引用存活时间短的对象。
分代名称 | 内存占用 | 垃圾收集频率 | 特点 |
---|---|---|---|
新生代 | 小 | 高 | minorGC,多次躲过回收后晋升到老年代 |
– | – | – | – |
老年代 | 大 | 低 | FullGC执行频率低,但是执行时间长。尽量 |
– | – | – | – |
永久代 | 小 | 低 | 废弃常量和无用类 |
Serial收集器
只使用一个处理器
minor GC 、 Full GC都 stop-the-world.
标记清除、标记压缩
适用于 对停顿时间要求不高,多个JVM实例在一台机器上。
Parellel收集器-吞吐量
吞吐量大。
尽最大可能收集。
Minor GC 并行。
Full GC 并行。
CMS收集器-低延迟
停顿少,低延迟
减少stop-the-world时间。
初始标记 a | 并发标记 b | 重新标记 c | 并发清除 d |
---|---|---|---|
短暂停顿,标记GC Roots可达对象① | 标记可从①对象达到的存活对象 | 再次停顿,重新标记b阶段导致未被标记的存活对象 | 清除整个Java堆 |
G1收集器-分代、垃圾优先
分代
优先回收垃圾最多的区域。
JVM性能调优
关注稳定状态下 内存使用,程序延迟,吞吐量
选择JVM运行模式
client、server
垃圾收集器
吞吐量、延迟、内存占用 三选二,因为不能三者都满足。
调整内存大小
调整JVM堆布局
调整新生代、老年代、永久代大小
初始堆空间大小配置
名称 | 设置参数 | 说明 |
---|---|---|
Java堆 | -Xms和-Xmx | 3-4倍FullGC后老年代空间量 |
– | – | – |
永久代 | -XX:permSize和-XX:MaxPermSize | 1.2-1.5倍~ |
– | – | – |
新生代 | -Xmn | 1-1.5倍~ |
– | – | – |
老年代 | Java堆大小减新生代大小 | 2-3倍~ |
Survivor
Survivor太小,导致to无法存放所有从Eden空间和“From”空间复制来的活跃对象,导致提升到老年代。 加速老年代内存消耗,提前Full GC.
-XX:SurvivorRatio=<ratio>
ratio表示单个survivor空间与Eden空间大小的比率。
如果是8,则表示 from:to:eden = 1:1:8
所以调大Survivor需要降低ratio。
survivor空间大小 = -Xmn/(-XX:SurvivorRatio=+2)
晋升
对象提升至老年代。
晋升阈值就是对象年龄。对象的年龄就是它所经历的Minor GC次数。
【最大晋升阈值参数】:-XX:MaxTenuringThreshold=<n>
对象年龄超过n值将其提升到老年代。
不建议将【最大晋升阈值】设置为0,这会导致刚分配的对象,会在接下来的Minor GC中直接从新生代晋升到老年代,导致老年代空间迅速增长以至于Full GC。
不成熟的(未到年龄)的晋升解决方案是:使用SurvivorRatio增大Survivor空间
当Survivor空间比较紧张,JVM会使用一个低于 最大晋升阈值(MaxTenuringThreshold)的值来保证目标Survivor空间的占用。
注意:Survivor空间过小,导致JVM 内部计算的阈值过小,导致易晋升到老年代,导致Full GC频繁。
监控晋升阈值
参数:-XX:+PrintTenuringDistribution
期望Survivor大小 小于 活下来的对象。导致Survivor空间溢出,提升对象到老年代。
至少将Survivor增大到 【存活的Survivor大小】
调整Survivor空间的容量
原则:调整Survivor空间的时候,如果新生代空间大小不变,增大Survivor则减少Eden。
因此,增大Survivor的同时,保持Eden空间不变,需要增大新生代空间。
理想状态:晋升阈值等于最大晋升阈值
如果Minor GC时间过长,就要减少新生代空间大小。
调整目标Survivor空间占用
尝试Minor GC之后仍然维持的Survivor空间占用
参数:-XX:TargetSurvivorRatio=<percent>
新生代
新生代大小决定了应用平均延迟
如果平均Minor GC持续时间大于应用程序平均延迟性要求,可以适当减小新生代空间大小;
如果Minor GC频率大于应用程序平均延迟性要求,可以适当增大新生代空间;
老年代
老年代大小决定了应用最差延迟
FullGC频率大于应用程序最大FullGC频率要求,可以适当增大老年代空间大小;
FullGC持续时间大于应用程序最差延迟性要求,可以使用CMS垃圾收集器;