美文网首页JVM 原理和实践
《Java 虚拟机原理》7.4 精选 —— FullGC 篇

《Java 虚拟机原理》7.4 精选 —— FullGC 篇

作者: 熊本极客 | 来源:发表于2021-02-26 22:19 被阅读0次

    FullGC 常见问题思考
    Q1:现网系统发生频繁 FullGC (约每10分钟一次),登陆机器发现 JVM 参数只配置了最大堆内存,其他配置都是系统默认配置,请问如何排查并优化 JVM 内存。
    Q2:系统出现 java.lang.OutOfMemoryError: Java heap space,如何排查和快速恢复?
    Q3:系统出现 java.lang.OutOfMemoryError: MetaSpace,如何排查和快速恢复?

    1.JVM 堆内存分配

    问题1:新生代跟老年代默认的比例分配
    问题2:新生代中的 Eden 区、FromSuv 区,ToSuv 区默认的分配比例

    JVM 内存参数说明
    -Xms 设置堆内存初始值堆内存 = 新生代 + 老年代,默认是物理内存的 1/64
    -Xmx 设置堆内存最大值,默认是物理内存的 1/4
    -Xmn 设置年轻代的大小
    -XX:SurvivorRatio=8 设置新生代中 Eden 和 Survivor 的比值。8 表示Survivor : Eden = 1 : 8,两个 FromSuv 和 ToSuv 占比相等,即 Eden : FromSuv : FromSuv = 8 : 1 : 1
    -XX:PretenureSizeThreshold=1024 设置对象进入老年区的阈值,即该对象大小超过 1024 KB 直接进入老年区
    -XX:MetaspaceSize 设置元空间初始值
    -XX:MaxMetaspaceSize 元空间最大值

    JVM 堆内存模型

    默认的 Young : Old = 1 : 2,例如 -Xmx2048m,-Xmn2014m。默认的 Eden : from : to = 8 : 1 : 1,即 -XX:SurvivorRatio=8

    image.png

    2.如何确定频繁 FullGC

    FullGC 频繁发生的特征:
    ① 系统的进程 CPU 接近 100%,经过 jstack 命令排查,发现多个垃圾回收线程的 CPU 占用很高
    ② 采用 jstat 命令查看 GC 情况,FullGC 的发生次数很多,同时次数不断增加

    (1)垃圾回收线程的 CPU 占用高

    // 使用 top 查看 CPU 占用情况
    // 结果可知, PID 为 9 CPU 占用是 98%
    [root@TG1704 log]# top
    top - 08:31:10 up 30 min,  0 users,  load average: 0.73, 0.58, 0.34
    KiB Mem:   2046460 total,  1923864 used,   122596 free,    14388 buffers
    KiB Swap:  1048572 total,        0 used,  1048572 free.  1192352 cached Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
        9 root      20   0 2557160 288976  15812 S  98.0 14.1   0:42.60 java
    
    // 使用 top -H -p 9 表示查看进程号 为 9 中的线程号
    // 结果可知,线程号为 10 的 CPU 占用是 79.3%,而 11 的 是 13.2%
    [root@TG1704 log]# top -H -p 9
    top - 08:31:16 up 30 min,  0 users,  load average: 0.75, 0.59, 0.35
    Threads:  11 total,   1 running,  10 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  3.5 us,  0.6 sy,  0.0 ni, 95.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem:   2046460 total,  1924856 used,   121604 free,    14396 buffers
    KiB Swap:  1048572 total,        0 used,  1048572 free.  1192532 cached Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
       10 root      20   0 2557160 289824  15872 R 79.3 14.2   0:41.49 java
       11 root      20   0 2557160 289824  15872 S 13.2 14.2   0:06.78 java
    
    // jstack -l 9 > jstack_9.log 表示进程号为 9 的堆栈信息保存到 jstack_9.log 文件
    // 结果可知,10 转换为十六进制 0xa,最后一行的 nid=0xa, VM Thread 指的就是垃圾回收的线程
    [root@TG1704 bin]# jstack -l 10 > jstack_10.log
    [root@TG1704 bin]# cat jstack_10.log
    "main" #1 prio=5 os_prio=0 tid=0x00007f8718009800 nid=0xb runnable [0x00007f871fe41000]
       java.lang.Thread.State: RUNNABLE
        at com.aibaobei.chapter2.eg2.UserDemo.main(UserDemo.java:9)
    
    "VM Thread" os_prio=0 tid=0x00007f871806e000 nid=0xa runnable
    

    (2)查看 GC 情况

    // jstat -gcutil 9 1000 5 表示查询进程号为 9,每 1000 毫秒显示 5 条
    // 结果可知,FullGC 次数高达 6793,同时不断增长
    [root@TG1704 bin]# jstat -gcutil 9 1000 5
      S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
      0.00   0.00   0.00  75.07  59.09  59.60   3259    0.919  6517    7.715    8.635
      0.00   0.00   0.00   0.08  59.09  59.60   3306    0.930  6611    7.822    8.752
      0.00   0.00   0.00   0.08  59.09  59.60   3351    0.943  6701    7.924    8.867
      0.00   0.00   0.00   0.08  59.09  59.60   3397    0.955  6793    8.029    8.984
    

    3.如何排查频繁 FullGC

    现网如果频繁发生的 FullGC,使得系统运行缓慢,导致系统不可用,那么首先采用 jstack 和 jmap 导出堆栈和内存信息,然后重启系统。

    (1)老年代发生 FullGC

    方案1: 内存调优

    根据 FullGC 后的老年代大小,调整堆内存、新生代、老年代和永久代
    JVM 堆,建议 3~4 倍 FullGC 后的老年代大小
    新生代,建议 1~1.5 倍 FullGC 后的老年代大小
    老年代,建议 2~3 倍 FullGC 后的老年代大小
    永久代即元空间,建议 1.2~1.5 倍 FullGC 后的老年代大小

    方案2:排查大对象和内存溢出

    如果上述的内存调优后,依然出现频繁 FullGC,需要采用内存分析工具,例如 MAT,分析 heapdump 日志。如下图所示,System、AppClassLoader 对象比较大,需要进一步分析业务代码中哪里创建或者频繁调用相关类。

    image.png

    (2)Metaspace 发生 FullGC

    Metaspace 的背景知识
    Metaspace 的作用:存储 JVM 加载的类信息、常量、静态变量等
    设置 Metaspace 的参数:-XX:MetaspaceSize=N -XX:MaxMetaspaceSize=N
    类加卸载的参数:-XX:+TraceClassLoading -XX:+TraceClassUnloading
    Metaspace 中的类被垃圾回收的条件:a.该类的所有实例被垃圾回收;b.该类对应的 java.lang.Class 对象没有被任何地方引用;c.加载该类的 ClassLoader 已经被垃圾回收;

    问题现象:在 flink session on k8s 模式下,每次启动 flink jar 作业都会使得 Metaspace 增长,最终导致 java.lang.OutOfMemoryError: Metaspace

    解法步骤:
    添加监控,例如 flink 容器内存、jobmanager heap 和 no-heap 内存、ClassLoader 加载类数量等监控信息。根据监控初步定位到,每次启动作业都会加载类的数量为 1.5k。
    ② 了解 flink 的类加载机制后,修改 flink-conf.yaml 配置的 classloader.resolve-order: parent-first。结果是作业每次启动还是增加 1.5k 的类,且 FullGC 无法回收 Metaspace 的内存,通过 MAT 分析可知 ClassLoader 较大且数量多。
    检查 flink jar 作业的业务流程,发现该 jar 是瘦包,即作业在启动过程中会加载自身 lib 目录下的 jar,导致 flink 的配置 classloader.resolve-order: parent-first 不起作用。因此,把该作业的打包方式修改为肥包,其结果是作业每次启动减少到了 0.9k 的类,但仍然会导致 Metaspace OOM,同时通过 MAT 分析可知 ClassLoader 较大且数量多。
    添加 JVM 参数:-XX:TraceClassLoading -XX:TraceClassUnloading,查看加载类的具体信息。从打印出的类信息可知,作业在加载自研类的同时也会加载该类的依赖,即类加载的全盘负责机制,使其加载了 0.9k 的类。

    参考

    [1] 谨防 JDK8 重复类定义造成的内存泄漏
    [2] 从一起 GC 血案谈到反射原理

    相关文章

      网友评论

        本文标题:《Java 虚拟机原理》7.4 精选 —— FullGC 篇

        本文链接:https://www.haomeiwen.com/subject/aswrfltx.html