JVM 性能调优基础

2018/08/12 JVM

JVM 性能调优基础

前言

本文章,只对自己总结,沉淀和自己的一些思考。

目录

  • Java 内存区域分布
  • 常用的垃圾收集器
  • 虚拟机性能监控工具
  • JVM 常用参数总结
  • 本次调优案例

二. 常用的垃圾收集器

可达性分析

以一个 GC Roots 的对象作为起始点,从这个节点开始向下搜索,当一个对象到GC Roots 没有任何引用的时候称作不可达,那么这个对象可以被回收。

可作为 GC Roots 的对象包括如下几种:

 * 虚拟机栈(栈帧中的引用变量表)中引用的对象
 * 方法区中静态属性,引用的对象
 * 方法区中常量引用的对象
 * 本地方法栈中JNI(既一般说的Native方法)引用的对象

垃圾收集算法

标记-清除算法

算法描述: 算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

算法不足:

  1. 一个是效率问题,标记和清除两个过程的效率都不高;
  2. 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
复制算法

算法描述:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。

算法缺点:将内存缩小为原来的一半,代价太高

现代模式:现在都使用这种算法来回收内存,不过不需要按照1:1的比例来划分内存空间,而是将内存划分为一块较大的 Eden 空间和两块较小的 Survivor 空间。每次使用一块 Eden 区和其中一块 Survivor 。当内存回收时,将 Eden和Survivor中还存活的对象一次复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot 虚拟机中 Eden 和 Survivor 的比例是 8:1。当Survivor空间不足时,需要依赖其他内存(这里指老年代)进行分配担保。

标记整理算法

算法描述:后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉,端之外的内存。

分代收集算法

算法描述:将对象的存活周期的不同,将内存划分为几块,一般将 Java 划分为 “新生代”和“老年代”,这样就可以根据各个年代的特点采用更加适合的算法。新生代一半存活率极低,那么利用“复制”算法,就可以把存活的少部分对象复制过来完成收集。但是对于老年代一般存活率极高,没有足够的空间进行分配担保。所以,老年代一把使用“标记-清除”或者“标记-整理”算法。

Serial 收集器(新生代收集器)

采用的算法:复制算法。

特点:单线程的收集器,在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

Serial old 收集器 (老年代收集器)

采用的算法:标记-整理算法。

特点:

  • 给 Client 模式下的虚拟机使用。
  • 在 Server 模式下,在JDK 1.5 以及之前的版本中与 Parallel Scavenge 收集器搭配使用。另一种用途是作为 CMS收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
ParNew 收集器 (新生代收集器)

采用的算法:复制算法。

特点:

  • Serial 的多线程版本。
Parallel Scavenge 收集器(新生代收集器)

算法:复制算法

目的:达到一个可控制的吞吐量。

释义:停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量可以高效率的利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需太多的交互任务。

CMS 收集器

步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

初始标记,重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下 GC Roots 能直接关联到的对象。速度很快,并发标记阶段就是进行 GC Roots Tracing 的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致的标记产生变动的那一部分对象的标记记录,这个阶段停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

缺点:

  1. 在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说 CPU 资源)而导致应用程序变慢,总吞吐量会降低。CMS 默认启动的回收线程数是 (CPU数量 + 3)/ 4 ,也就是当 CPU 在 4个以上时,并发回收垃圾收集线程不少于 25% 的 CPU 资源。并且随着 CPU 数量的增加而下降。但是当 CPU 不足 4 个(譬如2个)时,CMS 对用户程序的影响就可能变得很大,如果本来CPU负载就比较大,还分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了 50%。
  2. CMS 收集器无法处理浮动垃圾,可能出现 “Concurrent Mode Failure” 失败而导致另一次 Full GC 的产生。由于 CMS 垃圾收集器在收集的过程中,新的垃圾还在产生,这部分垃圾出现在标记过程之后, CMS 无法在档次收集中处理掉他们。所以,CMS 在收集的过程中,需要预留一部分空间提供并发收集时的程序运作使用。在CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿的时间就会很长。
  3. 由于 CMS 收集器,采用的是 “标记-清除” 算法,那么会产生很多空间碎片,这样就会给打对象分配带来很大麻烦,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。
G1 收集器

步骤:

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 刷选回收

初始标记阶段仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS 的值,让下一阶段用户程序并发运行时,能在正确可用的 Region 中创建对象,这阶段需要停顿线程,但是耗时很短。并发标记是从 GC Root 开始对堆中对象进行可达性分析。

三. 虚拟机性能监控工具

1. jps 虚拟机进程状况工具

作用:显示当前系统内所有的 HotSpot 虚拟机进程

命令:

jps [ option ] [hostid]

参数:

-q: 只输出本地虚拟机进程号
-m: 输出虚拟机进程启动时传递给主类 main() 函数的参数
-l: 输出主类的全名,如果是 Jar 包,输出 Jar 路径
-v: 输出虚拟机进程启动时 JVM 参数

2. jstat 虚拟机统计信息监视工具

命令:

jstat [ option  vmid [interval[s|ms] [count]]]

vmid: 虚拟进程 interval: 查询间隔 count: 查询次数

参数:

-class: 监视类装载,拆卸数量,总空间,以及类装载所耗费的时间
-gc:监视 Java 堆状况,包括 Eden 区,两个 survivor 区,老年代,元空间,类压缩,容量 及使用。
-gccapacity: 监视内容与 -gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大,最小空间。
-gcutil: 监视内容与 gc 基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause: 与 -gcutil 功能一样,但是会额外输出导致上一次 GC 的原因
-gcnew: 监视新生代GC 状况
-gcnewcapacity: 监视内容与 -gcnew 基本相同,输出主要关注使用到的最大,最小空间
-gcold: 监视老年代 GC 状况
-gcoldcapacity: 监视内容与 GC 基本相同,输出主要关注使用到的最大,最小空间

例如:

sudo jstat -class 4617 200 5
Loaded  Bytes  Unloaded  Bytes     Time   
 11548 22622.3        0     0.0      40.30
 11548 22622.3        0     0.0      40.30
 11548 22622.3        0     0.0      40.30
 11548 22622.3        0     0.0      40.30
 11548 22622.3        0     0.0      40.30
 
Loaded: 类加载数量
Bytes: 类加载千字节,单位 Kbytes (千字节)  
Unloaded: 类拆卸数量
Bytes: 类拆卸空间 单位 Kbytes (千字节)  
Time: 类加载和拆卸总耗时
sudo jstat -gc 4617 200 5
S0C      S1C     S0U   S1U      EC       EU        OC         OU       MC       MU    CCSC   CCSU     YGC    YGCT    FGC      FGCT     GCT   
78592.0 78592.0  0.0   473.7  629248.0 484665.6  262144.0   58403.2   72832.0 71702.8 8320.0 8001.4   8267  188.218   0      0.000  188.218
78592.0 78592.0  0.0   473.7  629248.0 495342.2  262144.0   58403.2   72832.0 71702.8 8320.0 8001.4   8267  188.218   0      0.000  188.218
78592.0 78592.0  0.0   473.7  629248.0 511233.5  262144.0   58403.2   72832.0 71702.8 8320.0 8001.4   8267  188.218   0      0.000  188.218
78592.0 78592.0  0.0   473.7  629248.0 524319.6  262144.0   58403.2   72832.0 71702.8 8320.0 8001.4   8267  188.218   0      0.000  188.218
78592.0 78592.0  0.0   473.7  629248.0 540289.0  262144.0   58403.2   72832.0 71702.8 8320.0 8001.4   8267  188.218   0      0.000  188.218

S0C: 当前 survivor 0 区的容量 (KB)
S1C: 当前 survivor 1 区的容量 (KB)
S0U: survivor 0 使用大小 (KB)
S1U: survivor 1 使用大小 (KB)
EC: 当前 eden 区的容量 (KB)
EU: eden 使用大小(KB)
OC: 当前老年代空间容量(KB)
OU: 当前老年代使用大小(KB)
MC: Metaspace 容量 (KB)
MU: Metaspace 使用(KB)
CCSC: 被压缩类的容量(KB)
CCSU: 被压缩类的使用大小(KB)
YGC: 年轻代垃圾收集次数
YGCT: 年轻代垃圾收集时间
FGC: Full GC 的次数
FGCT: Full GC 的时间
GCT: 整个垃圾收集的时间
sudo jstat -gccapacity 4617 200 5
 NGCMN    NGCMX     NGC     S0C     S1C      EC        OGCMN      OGCMX       OGC         OC        MCMN    MCMX      MC        CCSMN  CCSMX     CCSC    YGC      FGC 
786432.0 786432.0 786432.0 78592.0 78592.0 629248.0   262144.0   262144.0   262144.0   262144.0      0.0 1114112.0  73088.0      0.0 1048576.0   8320.0  10078     0
786432.0 786432.0 786432.0 78592.0 78592.0 629248.0   262144.0   262144.0   262144.0   262144.0      0.0 1114112.0  73088.0      0.0 1048576.0   8320.0  10078     0
786432.0 786432.0 786432.0 78592.0 78592.0 629248.0   262144.0   262144.0   262144.0   262144.0      0.0 1114112.0  73088.0      0.0 1048576.0   8320.0  10078     0
786432.0 786432.0 786432.0 78592.0 78592.0 629248.0   262144.0   262144.0   262144.0   262144.0      0.0 1114112.0  73088.0      0.0 1048576.0   8320.0  10078     0
786432.0 786432.0 786432.0 78592.0 78592.0 629248.0   262144.0   262144.0   262144.0   262144.0      0.0 1114112.0  73088.0      0.0 1048576.0   8320.0  10078     0

NGCMN: 初始化分配新生代最少容量(KB)
NGCMX: 初始化分配新生代最大容量(KB)
NGC: 当前新生代容量(KB)
S0C: 当前 survivor 0 容量(KB)
S1C: 当前 survivor 1 容量(KB)
EC: 当前 eden 容量(KB)
OGCMN: 初始化分配最小的老年代容量(KB)
OGCMX: 初始化分配最大的老年代容量(KB)
OGC: 当前老年代分配的容量(KB)
OC: 当前老年代容量(KB)
MCMN: 最小的元空间容量(KB)
MCMX: 最大元空间容量(KB)
MC: 元空间容量(KB)

jinfo Java 信息配置工具

可配置查看 JVM 配置

jstack: Java 堆栈跟踪工具

命令:

jstack [ option ] vmid

jstack 工具主要选项:

-F: 当正常输出的请求不被响应时,强制输出线程堆栈
-l: 除堆栈外,显示关于锁的附加信息
-m: 如果调到本地方法的话,可以显示 C++ 的堆栈

参考资料:

JDK Tools

Search

    Table of Contents