JVM - GC和GC Tuning

文章目录
  1. 1. 一、GC的基础知识
    1. 1.1. 什么是垃圾
    2. 1.2. 如何定位垃圾
    3. 1.3. 常见的垃圾回收算法
    4. 1.4. JVM内存分代模型(用于分代垃圾回收算法)
  2. 2. 二、垃圾回收器及调优
    1. 2.1. 常见的垃圾回收器
    2. 2.2. 常见垃圾回收器组合参数设定:(1.8)
    3. 2.3. JVM调优第一步,了解JVM常用命令行参数
    4. 2.4. 调优前的基础概念:
    5. 2.5. 什么是调优?
    6. 2.6. 调优,从规划开始
    7. 2.7. 解决JVM运行中的问题
    8. 2.8. Arthas在线排查工具
  3. 3. 三、常用参数
    1. 3.1. GC算法的基础概念
    2. 3.2. GC常用参数
    3. 3.3. Parallel常用参数
    4. 3.4. CMS常用参数
    5. 3.5. G1常用参数
  4. 4. 四、参考资料

一、GC的基础知识

什么是垃圾

没有任何引用指向的一个对象或者多个对象(循环引用)

如何定位垃圾

  1. 引用计数(ReferenceCount)
  2. 根可达算法(RootSearching)

常见的垃圾回收算法

  1. 标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
  2. 拷贝算法 (copying) - 没有碎片,浪费空间
  3. 标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)

JVM内存分代模型(用于分代垃圾回收算法)

  1. 部分垃圾回收器使用的模型
    除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
    G1是逻辑分代,物理不分代
    除此之外不仅逻辑分代,而且物理分代
  2. 新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace
    1. 永久代 元数据 - Class
    2. 永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
    3. 字符串常量 1.7 - 永久代,1.8 - 堆
    4. MethodArea逻辑概念 - 永久代、元数据
  3. 新生代 = Eden + 2个suvivor区
    1. YGC回收之后,大多数的对象会被回收,活着的进入s0
    2. 再次YGC,活着的对象eden + s0 -> s1
    3. 再次YGC,eden + s1 -> s0
    4. 年龄足够 -> 老年代 (-XX:MaxTenuringThreshold=X X默认是15)
    5. s区装不下 -> 老年代
  4. 老年代
    1. 顽固分子
    2. 老年代满了FGC Full GC
    3. 内存分配担保,在JVM在内存分配的时候,新生代内存不足时,把新生代的存活的对象搬到老生代,然后新生代腾出来的空间用于为分配给最新的对象。
  5. GC Tuning (Generation)
    1. 尽量减少FGC
    2. MinorGC = YGC
    3. MajorGC = FGC

二、垃圾回收器及调优

常见的垃圾回收器

常用垃圾回收器

  1. JDK诞生 Serial追随,为提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,并发垃圾回收是因为无法忍受STW。

  2. Serial:适用年轻代,串行回收

  3. PS(Parallel Scavenge) 适用年轻代,并行回收

  4. ParNew 适用年轻代,配合CMS的并行回收

  5. SerialOld 适用老年代

  6. ParallelOld 适用老年代,配合PS使用

  7. CMS(ConcurrentMarkSweep) 老年代,并发的,垃圾回收和应用程序同时运行,降低STW(Stop The World)的时间(200ms)
    CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定
    CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收
    算法:三色标记 + Incremental Update
    工作过程:初始标记,并发标记,重新标记,并发清理
    优点:并发收集,低停顿
    缺点:占用大量的CPU,无法处理浮动垃圾,出现Concurrent Mode Failure,空间碎片

  8. G1(10ms)
    算法:三色标记 + SATB (snapshot-at-the-beginning)
    优势:并行与并发,分代收集,空间整合,可预测的停顿
    步骤:初使标记,并发标记,最终标记,筛选回收

  9. ZGC (1ms)
    算法:ColoredPointers + LoadBarrier

  10. Shenandoah
    算法:ColoredPointers + WriteBarrier

  11. PS 和 PN区别的延伸阅读:
    https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html

  12. 垃圾收集器跟内存大小的关系

    1. Serial 几十兆
    2. PS 上百兆 - 几个G
    3. CMS - 20G
    4. G1 - 上百G
    5. ZGC - 4T - 16T(JDK13)
  13. 1.8默认的垃圾回收:PS + ParallelOld

常见垃圾回收器组合参数设定:(1.8)

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old
    • 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
  • -XX:+UseParNewGC = ParNew + SerialOld
    • 这个组合已经很少用(在某些版本中已经废弃)
  • -XX:+UseConcurrentMarkSweepGC = ParNew + CMS + Serial Old
  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
  • -XX:+UseG1GC = G1

JVM调优第一步,了解JVM常用命令行参数

  • JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

  • HotSpot参数分类
    标准: - 开头,所有的HotSpot都支持
    非标准:-X 开头,特定版本HotSpot支持特定命令
    不稳定:-XX 开头,下个版本可能取消
    java -X

    1. 区分概念:内存泄漏memory leak,内存溢出out of memory
    2. java -XX:+PrintCommandLineFlags -version 观查虚拟机配置
    3. java -XX:+PrintFlagsInitial 默认参数值
    4. java -XX:+PrintFlagsFinal 最终参数值
    5. java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
    6. java -XX:+PrintFlagsFinal -version |grep GC

调优前的基础概念:

  1. 吞吐量:吞吐量(CPU用于运行用户代码的时间与CPU消耗的总时间的比值)用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
  2. 响应时间:STW越短,响应时间越好

所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量…

问题:
科学计算,吞吐量。数据挖掘,thrput。吞吐量优先的一般:(PS + PO)
响应时间:网站 GUI API (1.8 G1)

什么是调优?

  1. 根据需求进行JVM规划和预调优
  2. 优化运行JVM运行环境(慢,卡顿)
  3. 解决JVM运行过程中出现的各种问题(OOM)

调优,从规划开始

  • 调优,从业务场景开始,没有业务场景的调优都是耍流氓

  • 无监控(压力测试,能看到结果),不调优

  • 步骤:

    1. 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
      1. 响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
      2. 吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]
    2. 选择回收器组合
    3. 计算内存需求(经验值)
    4. 选定CPU(越高越好)
    5. 设定年代大小、升级年龄
    6. 设定日志参数
      1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
      2. 或者每天产生一个日志文件
    7. 观察日志情况

解决JVM运行中的问题

  1. top命令观察问题:内存 & CPU 占用率。

  2. top -Hp 观察进程中的线程,哪个线程CPU和内存占比高

  3. jps(java process status) 参数 -mlv 定位具体java进程
    jstack 定位线程状况,重点关注:WAITING BLOCKED
    eg.
    waiting on <0x0000000088ca3310> (a java.lang.Object)
    假如有一个进程中100个线程,很多线程都在waiting on ,一定要找到是哪个线程持有这把锁
    怎么找?搜索jstack dump的信息,找 ,看哪个线程持有这把锁RUNNABLE

  4. 为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称
    怎么样自定义线程池里的线程名称?(自定义ThreadFactory)

  5. jinfo pid
    jinfo 是 JDK 自带的命令,可以用来查看正在运行的 java 应用程序的扩展参数,包括Java System属性和JVM命令行参数;也可以动态的修改正在运行的 JVM 一些参数。

  6. jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)
    jstat -gc|gcnew|gcold|gcutil pid 500 10 : 每个500个毫秒打印GC的情况,共打印10次退出
    jstat -class pid 观察加载类的数量
    jstat -options 查看所有options

  7. jmap 一个可以输出所有内存中对象的工具,甚至可以将VM 中的heap,以二进制输出成文本。
    jmap -heap pid 打印heap的概要信息,GC使用的算法,heap(堆)的配置及JVM堆内存的使用情况.
    jmap -histo pid | head -200, 将实例对象最多的200个类
    jmap -histo:live pid 在*:live前会进行full gc,带上live则只统计活对象,因此不加live的堆大小要大于加live堆的大小
    jmap -dump:format=b,file=xxx pid
    -dump 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件
    线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿,线上慎用(电商不适合用)
    1:-XX:+HeapDumpOnOutOfMemoryError 设定了参数HeapDump,OOM的时候会自动产生堆转储文件
    2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
    3:用在线定位

  8. 使用MAT / jhat /jvisualvm 进行dump文件分析
    Jhat参考资料:https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html

Arthas在线排查工具

  • 为什么需要在线排查?
    在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。
  • 观察jvm信息
  • thread定位线程问题
  • dashboard 观察系统情况
  • heapdump + jhat分析
  • jad反编译
    动态代理生成类的问题定位
    第三方的类(观察代码)
    版本问题(确定自己最新提交的版本是不是被使用)
  • redefine 热替换
    目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性
  • sc - search class
  • watch - watch method

三、常用参数

GC算法的基础概念

  • Card Table
    由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card
    在结构上,Card Table用BitMap来实现

GC常用参数

  • -Xmn -Xms -Xmx -Xss
    年轻代 最小堆 最大堆 栈空间
  • -XX:+UseTLAB
    使用TLAB(Thread Local Allocation Buffer),默认打开
  • -XX:+PrintTLAB
    打印TLAB的使用情况
  • -XX:TLABSize
    设置TLAB大小
  • -XX:+DisableExplictGC
    System.gc()不管用 ,FGC
  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintHeapAtGC
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime (低) 打印暂停时长
  • -XX:+PrintReferenceGC (重要性低)记录回收了多少种不同引用类型的引用
  • -verbose:class 类加载详细过程
  • -XX:+PrintVMOptions
  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用
  • -Xloggc:/opt/log/gc.log
  • -XX:MaxTenuringThreshold 升代年龄,最大值15
  • -XX:PretenureSizeThreshold=1M //大对象直接存入老年代,默认值是0,意思是不管多大都是先在eden中分配内存
  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 这些不建议设置

Parallel常用参数

  • -XX:SurvivorRatio
  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

CMS常用参数

  • -XX:+UseConcMarkSweepGC
  • -XX:ParallelCMSThreads CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction
    使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection
    在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction
    多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled
  • -XX:CMSInitiatingPermOccupancyFraction
    达到什么比例时进行Perm回收
  • GCTimeRatio
    设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis
    停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
  • -XX:LargePageSizeInBytes=64m 内存分页

G1常用参数

  • -XX:+UseG1GC
  • -XX:MaxGCPauseMillis
    建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:GCPauseIntervalMillis
    设置停顿间隔时间
  • -XX:+G1HeapRegionSize
    分区大小,建议逐渐增大该值,1 2 4 8 16 32。
    随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长
    ZGC做了改进(动态区块大小)
  • G1NewSizePercent
    新生代最小比例,默认为5%
  • G1MaxNewSizePercent
    新生代最大比例,默认为60%
  • GCTimeRatio
    GC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads
    线程数量
  • InitiatingHeapOccupancyPercent
    启动G1的堆空间占用比例

四、参考资料

  1. https://docs.oracle.com/en/java/javase/13/
  2. https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
  3. http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
  4. JVM调优参考文档:https://docs.oracle.com/en/java/javase/13/gctuning/introduction-garbage-collection-tuning.html#GUID-8A443184-7E07-4B71-9777-4F12947C8184
  5. https://www.oracle.com/technical-resources/articles/java/g1gc.html
  6. Arthas:https://github.com/alibaba/arthas
    1. 启动arthas java -jar arthas-boot.jar
    2. 绑定java进程
    3. dashboard命令观察系统整体情况
    4. help 查看帮助
    5. help xx 查看具体命令帮助