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 system

    1.2 jstack

    1.3 jmap

2. 垃圾回收

我们进行MinorGC,会不会有对象被老年代引用着?进行OldGC会不会又有对象被年轻代引用着?

那我们进行MinorGC的时候不光要管GC Roots,还有再去遍历老年代,这个性能问题就很大了。

解决方案:

跨代引用相对于同代引用来说仅占极少数

由此就产生了一个新的解决方案,我们不用去扫描整个老年代了,只要在年轻代建立一个数据结构,叫做记忆集Remembered Set,他把老年代划分为N个区域,标志出哪个区域会存在跨代引用。以后在进行MinorGC的时候,只要把这些包含了跨代引用的内存区域加入GC Roots一起扫描就行了。

卡表实际上就是记忆集的一种实现方式,如果说记忆集是接口的话,那么卡表就是他的实现类。

实际上卡表就是映射了一块块的内存地址,这些内存地址块称为卡页

只要一个卡页内的对象存在一个或者多个跨代对象指针,就将该位置的卡表数组元素修改为1,表示这个位置为脏,没有则为0。

在GC的时候,就直接把值为1对应的卡页对象指针加入GC Roots一起扫描即可。

三色标记

  1. 白色,在刚开始遍历的时候,所有的对象都是白色的
  2. 灰色,被垃圾回收器扫描过,但是至少还有一个引用没有被扫描
  3. 黑色,被垃圾回收器扫描过,并且这个对象的引用也全部都被扫描过,是安全存活的对象

三色标记的问题

存活对象标记成需要清理,同时满足一下两种场景才会出现

  1. 插入了一条或者多条黑色到白色对象的引用
  2. 删除了全部从灰色到白色对象的引用

决绝方案:

增量更新原始快照 : 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)

  1. 因为增量更新之后会重新深度扫描,G1是以region的方式存储对象,而CMS是以一个连续的老年代存储对象,G1会涉及到跨代扫描,G1的代价相对于CMS要高。
  2. 而且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 融合之后的改动之一。

  1. JDK 1.6 及之前:方法区使用永久代实现,静态变量存放在永久代
  2. JDK 1.7 :“去永久代”的前置版本,还存在永久代,不过已经将字符串常量池和静态变量从永久代移到了堆上
  3. 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