美文网首页
JVM GC&调优

JVM GC&调优

作者: _空格键_ | 来源:发表于2020-07-03 12:22 被阅读0次

1. 概述

内存泄漏:一块内存被占用后未释放,程序后续不在使用这块内存内容,导致这块内存一直被占用。
内存溢出:OOM,一个程序或进程分配的内存(内存或堆栈)不够使用,导致无法继续运行。大多数般情况下,内存泄漏都会导致OOM。

JVM调优就是通过调整一堆参数,来合理配置堆栈大小垃圾回收器,使得程序运行的稳定、高效。

2. 垃圾回收(Garbage Collection)

2.1. 垃圾识别

我们将无用的引用对象称之为垃圾,那么GC如何区分有用的和无用的对象/数据引用呢?

两种方式:
1. 引用计数(Reference Count)

引用计数算法模型.png 环状垃圾.png

优点:简单,古老
缺点:对于环状的引用垃圾无法识别

2. 根可达(Root Searching)

根可达算法模型.png

什么是根root,JVMS(The Java Virtual Machine Specification)中定义如下:

  • JVM stacks - 虚拟机栈中引用的对象
  • native method stacks - 本地方法栈中引用的对象 JNI
  • run-time constant pool - 常量池中常量引用的对象
  • static references in method area, class - 类或方法中静态属性的引用对象
  • 等等

JVM使用的就是“根可达”算法

2.2. 垃圾回收算法

以“根可达”算法派生的三种基础算法:

  1. 标记清除(Mark-Sweep)
    从每个root出发,依次标记有引用关系的对象,没有标记的就是垃圾,最后将没有标记的对象清除。
    优点:简单,速度快
    缺点:随时间推移,内存碎片化严重,有可能大的对象找不到一块合适内存分配,易触发FGC。
Mark-Sweep.png
  1. 标记拷贝(Mark-Copy)
    内存一份为二,每次只激活使用其中一块。回收时,将当前块中标记的有用对象快速拷贝到未使用的块中,完成后未使用块激活,使用块标记为未激活;下次清理时两块再次切换。JMM中的 Survivor 就是使用这个,在S0/S1之间来回切换。
    优点:速度极快,没有碎片化产生
    确定:内存利用率不高,至少有一半内存空闲
Mark-Copy.png
  1. 标记整理(Mark-Compact)
    综合 1 2 优点,先标记出有用的块,然后将存活的对象整理到内存空间一端,使其形成连续的已使用空间,最后把已使用空间之外的全部区域清理掉。
    缺点:效率较低
Mark-Compact.png

以上三种垃圾回收算法的不通运用于组合,产生了十种垃圾回收器

2.3. JVM内存模型

介绍垃圾回收器回收器之前先回顾下JVM内存模型:包含类加载器,执行器(解释执行,JIT编译执行),运行时内存布局。

注意:JVM内存模型与Java内存模型(JMM)是两个概念,JMM规范Java虚拟机与计算机内存如何协同工作,规定一个线程如何和何时可以看到其他线程修改过的共享变量值,以及在必须时如何同步共享变量。即程序如何保证操作的原子性、可见性、有序性。

JVM内存布局.png JVM中对象创建分配管理流程.png

TLAB(Thread Local Allocation Buffer)在Eden中开辟了一小块线程私有的区域内存,为线程独享。默认设定为占用Eden Space的1%。

在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享,适合被快速GC(栈中弹出)

对象分配内存,为保证分配原子性,需要对区域加锁、失败重试(CAS)等方式。

2.4. 垃圾回收器(Garbage Collector)

十种垃圾回收器.png

JDK1.8 默认使用 ParallelScavenge + ParallelOld,生产上小于8G一般使用ParNew+CMS,8G及以上推荐使用G1

前六种组合方式:

  • Serial+SerialOld
  • ParallelScavenge+ParallelOld
  • ParNew+CMS

2.4.1. Serial回收器

Serial是比较早期使用的垃圾收集器,工作于年轻代中,工作时 STW(stop the world),使用 Mark-Copy 算法的单线程垃圾收集器。应用于Young区域(YGC)

2.4.2. Serial Old回收器

与Serial类似,只不过它工作于老年代,也具有 STW 性质,使用Mark-Sweep标记清除或Mark-Compact标记整理算法的单线程垃圾收集器。

Serial+SerialOld往往组合使用,但在随着内存逐渐增大后,由于使用的是单线程进行GC,会导致STW的时间过于长,给用户的表现就是卡顿,卡死的状态。为此,诞生了并行的垃圾收集器

2.4.3. Parallel Scavenge回收器

一个具有 STW 性质,使用 Mark-Copy 算法的多线程垃圾收集器。工作于年轻代。

2.4.4. Parallel Old回收器

一个具有 STW 性质,使用 Mark-Compact 算法的多线程垃圾收集器。工作于老年代。

ParallelScavenge+ParallelOld往往组合使用,jdk1.8的默认。但并不是线程越多就能使STW的时间缩短,因为受限于CPU核数,若多于CPU核数,则会很多时间浪费在线程上下文切换上。

2.4.5. ParNew回收器

一个具有 STW 性质,使用 Mark-Copy 算法的多线程垃圾收集器。工作于年轻代。由于Parallel Scavenge与CMS配合不好,因此诞生了专门用于配合CMS垃圾收集器的ParNew垃圾收集器。

2.4.6. CMS回收器

CMS(Concurrent Mark Sweep)是一个并发收集器,STW停顿时间较短,使用 Mark-Sweep 算法。GC线程与工作线程可同时运行。

CMS回收器.png

CMS有四个阶段:

  1. 初始标记(Initial Mark):仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要短暂停顿。
  2. 并发标记(Concurrent Mark):进行GC Roots Scan的过程,它在整个回收过程中耗时最长,不需要停顿,可与工作线程同时运行。
  3. 重新标记(Remark):修正并发标记中漏标的对象,需要STW。
  4. 并发清除(Concurrent Sweep):清理,无需停顿。

注:并发标记过程中会存在错标问题(本来是垃圾,但是标记完后,系统又产生了对它的引用)与漏标问题(本来不是垃圾,但在标记完后,不再有该对象的引用,即已经是垃圾,称之为浮动垃圾)。漏标问题可以在下一次GC就可以解决。而错标问题,则是CMS、G1、ZGC等最新的垃圾收集器所关注并要解决的问题,而这类垃圾收集器的区别就在于怎么解决错标问题。CMS和G1采用的都是三色标记法,而ZGC采用的是颜色指针法。

并发标记算法:三色扫描算法
三色标记完后,因为并发,可能存在漏标或错标的,需要进一步修正,
CMS使用三色标记 + Incremental Update修正

2.4.6.1. 三色标记

三色标记算法中的三色(白 灰 黑)是逻辑上的概念。如下图所示,

三色标记模型.png

A自己已经访问过了,同时它的成员变量也已经都访问过了(即标记完成),则此时A为黑色;
B自己已经标记过了,但是成员变量尚未标记,此时B为灰色;
D是没有标记过得节点,此时为白色。

2.4.7. G1回收器

G1(Garbage-First Garbage Collector),与CMS相比,G1具有压缩功能,避免碎片问题,G1的暂停时间更加可控。G1是1.7推出,1.8成熟。

G1模型.png

G1将堆分成若干大小相同的小块,即region,每个region可被标记为Edge、Survivor、Old、Humongous四种类型(Humongous是特殊的Old,可以跨越连续的region,用来存特大对象),未标记代表空闲区域。回收时,G1采用 Mark-Copy 优先回收垃圾最多的region,因此不会产生碎片,并停顿时间可预测。jdk11已将G1作为默认GC。

G1有五个阶段:

  1. 初始标记(Initial Mark):仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要短暂停顿。
  2. 根区域扫描(Root Region Scan):不会STW,并发标记从上一阶段扫描被引用的老年代对象。
  3. 并发标记(Concurrent Mark):与CMS类似。
  4. 重新标记(Remark):需要STW,修正并发标记中漏标的对象,完成最终标记。
  5. 并发清除(Concurrent Sweep):清理,无需停顿。

G1使用三色标记 + SATB修正(为了配合G1的RSet,效率更高)

2.4.8. 以上回收器对比

回收器 串行/并行 分代 算法 适用内存 目标 适应场景
Serial 串行 Y Mark-Copy 几十M 速度优先 单CPU环境下的Client模式
Serial Old 串行 O Mark-Sweep / Mark-Compact 几十M 速度优先 单CPU环境下的Client模式,CMS降级预案
Parallel Scavenge 并行 Y Mark-Copy 几个G 吞吐量优先 在后台运算,没有太多交互任务
Parallel Old 并行 O Mark-Compact 几个G 吞吐量优先 在后台运算,没有太多交互任务
ParNew 并行 Y Mark-Copy 几个G 速度优先 多CPU,Server模式,同CMS搭配
CMS 并发 O Mark-Sweep 20G 速度优先 多用与网站,B/S模型的服务器
G1 并发 YO Mark-Copy 上百G 速度优先 大内存,低延迟,高吞吐的服务器
ZGC 并发 YO - 4T - 16T 速度优先 -

并发:串行+并行
Y:年轻代 O:老年代

3. 参数调整

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

    标准: - 开头,所有的HotSpot都支持,如 java -version

    非标准:-X 开头,特定版本HotSpot支持特定命令 java -Xmixed

    高级:-XX 开头,也是不稳定的,下个版本可能取消 java -XX:+UseG1GC

    参数类型:

    1. boolean类型: + 表示true添加,- 表示false取消;
    2. 非boolean类型(key=value):使用 -XX:xx=value 形式设置

java -XX:+PrintFlagsWithComments //只有debug版本能用

假如运行了 java HelloGC 程序:

  1. java -XX:+PrintCommandLineFlags HelloGC 打印命令行参数(可以看默认垃圾回收器)
  2. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
    PrintGCDetails PrintGCTimeStamps PrintGCCauses
  3. java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
  4. java -XX:+PrintFlagsInitial 默认参数值
  5. java -XX:+PrintFlagsFinal -version 最终参数值(= 没有修改过 := 人为修改过)

下面介绍部分参数,以jdk1.8为例。

3.1. GC参数

推荐网站:https://perfma.com/

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

设定日志参数
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause

3.2. 堆内存参数

一般生产上,会把最小内存和最大内存设置一样,避免JVM堆栈弹性收缩扩展,影响服务性能。

JVM内存参数设置.png

4. 问题排查

内存溢出OOM,CPU飙高100%

一般四步:

  1. 获取进程号PID
  2. 根据PID找到线程
  3. 找到线程堆栈
  4. 根据堆栈信息,结合代码和应用场景等,来详细分析排查原因(最难最耗时的环节,经验很重要)

4.1. 工具介绍

4.1.1. 命令工具

JDK自带了一些工具命令

  1. jps :查看当前机器jvm进程
  2. jinfo :查实时参数信息,并可修改
  3. jmap :生成推存储快照,该命令一执行,JVM就暂停,生产一般不用
  4. jstat :查询JVM性能统计信息
  5. jstack : 查询进程/线程的堆栈信息。

4.1.2. 可视化工具

  1. jconsole : jdk自带,一般用于本地开发环境测试。
  2. jvisualvm : jdk自带,远程连接
  3. jhat : jdk自带,将dump文件以html形式展示
  4. jprofiler :(收费)
  5. arthas : 阿里出的,在线排查工具,通过命令操作。需要安装到服务器,一般生产上运维可以这么搞。
  6. Memory Analyer Tools(MAT):eclipse的内存分析工具

4.2. OOM

OOM不用看就是java程序,jps查看进程号

收集dump文件(一般生产都会配置 -XX:+HeapDumpOnOutOfMemoryError 出现OOM时自动打印dump文件)

分析dump,找到实例数最多,占用内存过多的对象

从这些实例对象入手,分析原因

4.3. CPU飙高

CPU飙高一般都是程序运行出现 while(true) 或类似while(true)的情况。比如死锁,链表上出现环遍历等。

定位思路:
查进程:看下是哪个应用导致在大量消耗CPU资源,top + jps 查看进程号
查线程:定位是哪个线程
查堆栈:根据堆栈信息,综合分析,猜测可能产生的原因
查源码:根据堆栈信息反查源码,分析哪些可疑代码在哪些场景下可能出现死循环

5. 参考

HotSpot虚拟机垃圾收集优化指南

Arthas 用户文档

相关文章

  • JVM GC&调优

    1. 概述 内存泄漏:一块内存被占用后未释放,程序后续不在使用这块内存内容,导致这块内存一直被占用。内存溢出:OO...

  • 3.JVM调优工具

    JVM调优工具 1、JVM调优工具-JDK工具 1.1 jps jps:Java Virtual Machine ...

  • JVM调优高频面试

    JVM调优目的 使用较小的内存占用来获得较高的吞吐量或者较低的延迟。 一、JVM内存调优 对JVM内存的系统级调优...

  • Spark(十八)JVM调优之原理概述以及降低cache操作的内

    一、调优背景 1、常规性能调优:分配资源、并行度。。。等 2、JVM调优(Java虚拟机):JVM相关的参数,通常...

  • 2019-10-12 jvm调优

    JVM调优总结

  • 18家大厂Java面试题整理了350道(分布式+微服务+高并发)

    一、性能调优系列 1.Tomcat性能调优 JVM参数调优:-Xms 表示JVM初始化堆的大小,-Xmx 表示J...

  • JVM 高频面试题

    本章面试题如下:JVM三大性能调优参数,JVM 几个重要的参数JVM调优JVM内存管理,JVM的常见的垃圾收集器,...

  • JVM调优

    1 调优层次 性能调优包含多个层次,比如:架构调优、代码调优、JVM调优、数据库调优、操作系统调优等。架构调优和代...

  • JVM性能调优

    JVM性能调优 JVM性能监控工具介绍

  • (六)、jvm调优

    2018-10-03 推荐原文 原文作者:纯洁的微笑 什么是jvm调优呢?jvm调优就是根据gc日志分析jvm内存...

网友评论

      本文标题:JVM GC&调优

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