JVM
1.工具
jps
jinfo : 查看配置
jvisualvm
jconsole
1.1 jmap
jmap [options] pid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system1.2 jstack
1.3 jmap
2. 垃圾回收
我们进行MinorGC,会不会有对象被老年代引用着?进行OldGC会不会又有对象被年轻代引用着?
那我们进行MinorGC的时候不光要管GC Roots,还有再去遍历老年代,这个性能问题就很大了。
解决方案:
跨代引用相对于同代引用来说仅占极少数。
由此就产生了一个新的解决方案,我们不用去扫描整个老年代了,只要在年轻代建立一个数据结构,叫做记忆集Remembered Set,他把老年代划分为N个区域,标志出哪个区域会存在跨代引用。以后在进行MinorGC的时候,只要把这些包含了跨代引用的内存区域加入GC Roots一起扫描就行了。
卡表实际上就是记忆集的一种实现方式,如果说记忆集是接口的话,那么卡表就是他的实现类。
实际上卡表就是映射了一块块的内存地址,这些内存地址块称为卡页
只要一个卡页内的对象存在一个或者多个跨代对象指针,就将该位置的卡表数组元素修改为1,表示这个位置为脏,没有则为0。
在GC的时候,就直接把值为1对应的卡页对象指针加入GC Roots一起扫描即可。
三色标记
- 白色,在刚开始遍历的时候,所有的对象都是白色的
- 灰色,被垃圾回收器扫描过,但是至少还有一个引用没有被扫描
- 黑色,被垃圾回收器扫描过,并且这个对象的引用也全部都被扫描过,是安全存活的对象
三色标记的问题
存活对象标记成需要清理,同时满足一下两种场景才会出现
- 插入了一条或者多条黑色到白色对象的引用
- 删除了全部从灰色到白色对象的引用
决绝方案:
增量更新和原始快照 : CMS使用的是增量更新,而像G1则是使用原始快照
增量更新解决方案就是,他会把这些新插入的引用记录下来,扫描结束之后,再以黑色对象为根重新扫描一次。
原始快照则是去破坏第二个条件,他把这个要删除的引用记录下来,扫描结束之后,以灰色对象为根重新扫描一次。所以就像是快照一样,不管你删没删,其实最终还是会按照之前的关系重新来一次
G1使用场景:
1、50%以上的堆被存活对象占用:当大多数对象都存活的时候,说明老年代被占用的比例也会很大,这个时候就会触发full gc,full gc是很慢的,如果我们使用G1,那么G1就会触发mixed gc,而且mixed gc的GC最大停顿时间还是可控的。
2、对象分配和晋升的速度变化非常大:说明了对象往老年代挪动的频率很频繁,一样的,可以减少full gc的发生。
3、垃圾回收时间特别长,超过1秒:可以设置停顿时间,提升用户体验。
4、8GB以上的堆内存(建议值):内存如果在8G以下,收集的垃圾不是很多,而G1的算法相对于CMS较为复杂,还很有可能效率不如CMS,但是对于大内存,STW时间比较长,所以,在可控停顿时间这里,G1比较合适。
5、停顿时间是500ms以内:停顿时间可由用户控制。
为什么CMS用增量更新,G1用原始快照(SATB)
- 因为增量更新之后会重新深度扫描,G1是以region的方式存储对象,而CMS是以一个连续的老年代存储对象,G1会涉及到跨代扫描,G1的代价相对于CMS要高。
- 而且G1较CMS更强调用户体验,重新深度扫描会加大STW时间,所以G1选择原始快照。
3.调优
3.1 JVM调优的时机
- Heap内存(老年代)持续上涨达到设置的最大内存值;
- Full GC 次数频繁;
- GC 停顿时间过长(超过1秒);
- 应用出现OutOfMemory 等内存异常;
- 应用中有使用本地缓存且占用大量内存空间;
- 系统吞吐量与响应性能不高或下降。
3.2 JVM调优的目标
- 延迟:GC低停顿和GC低频率;
- 低内存占用;
- 高吞吐量;
2.3 JVM调优的步骤
- 分析系统系统运行情况:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
- 确定JVM调优量化目标;
- 确定JVM调优参数(根据历史JVM参数来调整);
- 依次确定调优内存、延迟、吞吐量等指标;
- 对比观察调优前后的差异;
- 不断的分析和调整,直到找到合适的JVM参数配置;
- 找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。
4.问题
4.1 为什么用元空间替代永久代?
- 降低OOM
- 降低维护成本,元空间采取本地内存,不需要单独调优参数
永久代和元空间都是 HotSpot 虚拟机对《Java 虚拟机规范》中方法区的实现,在 JDK 1.8 之前 HotSpot 是使用永久代来实现方法区的,但这样会导致 JVM 调优比较困难,且容易发生 OOM 的问题,而 JDK 1.8 及之后,使用的是元空间存放在本地内存中的方式来替代永久代的,这样就降低了 OOM 发生的可能性,也是 JRockit 和 HotSpot 融合之后的改动之一。
- JDK 1.6 及之前:方法区使用永久代实现,静态变量存放在永久代;
- JDK 1.7 :“去永久代”的前置版本,还存在永久代,不过已经将字符串常量池和静态变量从永久代移到了堆上;
- JDK 1.8 及以后:无永久代,使用元空间(存放在本地内存中)实现方法区,常量保存在元空间,但字符串常量池和静态变量依然保存在堆中。
4.2 CMS
- 初始标记
- 并发标记
- 重新标记
- 并发清除
CMS缺点:
吞吐量低,对 CPU 资源消耗较大 ,无法处理浮动垃圾(漏标+并发清除阶段产生),采用标记清除算法产生内存碎片,并发模式失败后切换 seria old
5.G1 垃圾回收器
第一种:年轻代垃圾回收 YoungGC
YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC。
第二种,混合垃圾回收,MixedGC
不是FullGC,老年代的堆占有率达到设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC。
第三种,Full GC
停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。(Shenandoah优化成多线程收集了
4.1 G1垃圾收集器优化建议
调优 -XX:MaxGCPauseMills 时间,避免频繁youngGC 和 大量对象快速进入老年代。
5.实际使用jvm调优配置参考
2.1 通用型:
开发(模板4)+测试(模板4)+预发(模板2)+生产(模板2)
2.2 并发型:
开发(模板3)+测试(模板3)+预发(模板1)+生产(模板1)
2.3 IO密集型(测试中,如需配置请联系架构师评估):
开发(模板3)+测试(模板3)+预发(模板5)+生产(模板5)
3 ParNew+CMS垃圾回收器模板
模板1:Pod:CPU request:256m limit:2048m;内存 request:3584M limit 4096M ;弹性伸缩 CPU:480%
-Xms2560M -Xmx2560M -Xmn1920M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+CMSScavengeBeforeRemark
模板2:Pod:CPU request:256m limit:1024m;内存 request:1536M limit 2048M;弹性伸缩 CPU:240%(常规配置,limit*0.6的位置弹性)
-Xms1024M -Xmx1024M -Xmn768M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+CMSScavengeBeforeRemark
模板3:Pod:CPU request:128m limit:512m;内存 request:768M limit 1024M;弹性伸缩 CPU:240%
-Xms448M -Xmx448M -Xmn336M -XX:SurvivorRatio=4 -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+CMSScavengeBeforeRemark
模板4:Pod:CPU request:128m limit:512m;内存 request:640M limit 1024M;弹性伸缩 CPU:240%
-Xms384M -Xmx384M -Xmn288M -XX:SurvivorRatio=4 -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+CMSScavengeBeforeRemark
模板5:Pod:CPU request:1024m limit:4096m;内存 request:6144M limit 8192M;弹性伸缩 CPU:240%
-Xms6144M -Xmx6144M -Xmn4608M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+CMSScavengeBeforeRemark