目录
一、引言
二、基本介绍
三、JVM基础
1. java堆
2. 垃圾回收
3. STW
四、调优层次
五、调优指标
六、JVM调优原则
1. 优先原则
2. 堆设置
3. 垃圾回收器设置
1> GC 发展阶段
2> G1的适用场景
3> 其他收集器适⽤场景
4. 年轻代设置
5. 年⽼代设置
6. 方法区设置
七、JVM调优步骤
1. 监控分析
1.1. 如何⽣成GC⽇志
1.2. 如何产⽣dump⽂件
1> JVM的配置⽂件中配置
2> jmap生成
3> 第三⽅可视化⼯具⽣成
4> 知识小结
2. 判断
3. 确定目标
4. 调整参数
5. 对⽐调优前后指标差异
6. 重复以上过程
7. 应⽤
一、引言
当Java程序性能达不到⽬标,且架构上的优化、代码上的优化⼿段都已经穷尽时,通常需要调整垃
圾回收器和JVM内存空间配置来进⼀步提⾼性能,这就是JVM调优。JVM性能调优是个⽼⽣常谈的
话 题,本⽂就⼀步步带你系统地探寻JVM调优。
本⽂通过理论加实践的⽅式逐步讲解JVM调优,共分四部分。
- 第⼀部分:基本介绍,主要讲解关于JVM调优的理论概念知识;
- 第⼆部分:JVM调优⼯具,主要讲述在调优过程中所使⽤的⼯具以及命令;
- 第三部分:JVM参数,主要讲述JVM主要调优参数;
- 第四部分:案例分析,通过前三章的理论基础结合真实项⽬案例来看看如何进⾏调优
二、基本介绍
本章主要通过JVM基础、调优层次、调优指标、何时JVM调优、JVM调优⽬标、JVM调优原则以及
JVM调优步骤介绍,以达到对JVM调优有个整体上的认识,后续通过具体的案例结合理论来看下如
何进行调优。
三、JVM基础
在讲JVM调优之前,先简单回顾下JVM相关的基础知识,这⾥我们重点回顾下JAVA堆、垃圾回收
器。这两块也是在JVM调优过程中重点关注的部分。
1. java堆
被所有线程共享,在虚拟机启动时创建,⽤来存放对象实例,⼏乎所有的对象实例都在这⾥分配
内 存。
对于⼤多数应⽤来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最⼤的⼀块。
Java堆是垃 圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。
如果从内存回收的⻆度看,由于现在收集器 基本都是采⽤的分代收集算法,所以java堆中还可以细
分为:新⽣代和⽼年代;新⽣代⼜有Eden空间、From Survivor空间、To Survivor空间三部分。
java 堆不需要连续内存,并且可以通过动态增加其内 存,增加失败会抛出 OutOfMemoryError 异
常。
2. 垃圾回收
针对HotSpot VM的实现,它⾥⾯的GC其实准确分类只有两⼤种:
- Partial GC:并不收集整个GC堆的模式。其中⼜分为
-
- 新⽣代的回收:(Minor GC/Young GC),只收集新⽣代的GC
- ⽼年代的回收:(Major GC/Old GC),只收集⽼年代的GC。 ⽬前只有CMS的concurrent collection是这个模式,只收集⽼年代。
- Mixed GC:收集整个young gen以及部分old gen的GC。 只有G1有这个模式。
- Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等所有部分的模 式。
Major GC通常是跟full GC是等价的,收集整个GC堆。
但因为HotSpot VM发展了这么多年, 外界对各种名词的解读已经完全混乱了,
当有⼈说“major GC”的时候⼀定要问清楚他想要指的是上 ⾯的full GC还是old GC。
3. STW
Stop-the-World,简称STW,指的是GC事件发⽣过程中,会产⽣应⽤程序的停顿。
停顿是产⽣时整 个应⽤程序线程会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为
STW。
Stop-the-world意 味着 JVM由于要执⾏GC⽽停⽌了应⽤程序(⽤户线程、⼯作线程)的执⾏,并且
这种情形会在任何⼀种GC算法中发⽣。
当Stop-the-world发⽣时,除了GC所需的线程以外,所有线程都处于等待状态直到GC任 务完成。
STW事件和采⽤哪款GC⽆关,所有的GC都有这个事件。哪怕是G1也不能完全避免Stop-the-world
情况发⽣,只能说垃圾回收器越来越优秀,回收效率越来越⾼,尽可能缩短了暂停时间。
STW是JVM在后台⾃动发起和自动完成的,在⽤户不可⻅的情况下,把⽤户正常的⼯作线程全部
停 掉。
随着应⽤程序越来越复杂,每次GC不能保证应⽤程序的正常运⾏。⽽经常造成STW的GC跟不上
实 际的需求,所以才需要不断对GC进行优化。事实上,GC优化很多时候就是指减少Stop-the-
world发⽣ 的时间,从⽽使系统具有⾼吞吐 、低停顿的特点。
四、调优层次
性能调优包含多个层次,⽐如:架构调优、代码调优、JVM调优、数据库调优、操作系统调优等。
架 构调优和代码调优是JVM调优的基础,其中架构调优是对系统影响最⼤的。
五、调优指标
- 吞吐量:运⾏⽤户代码的时间占总运⾏时间的⽐例 (总运⾏时间=程序的运⾏时间+内存回收的时 间);
- 暂停时间:执⾏垃圾收集时,程序的⼯作线程被暂停的时间;
- 内存占⽤:java堆区所占的内存⼤⼩;
注重吞吐量:吞吐量优先,意味着在单位时间内,STW的时间最短:0.2 + 0.2= 0.4 s
注重低延迟:暂停时间优先,意味这尽可能让单次STW的时间最短:0.1 + 0.1 + 0.1 + 0.1 + 0.1 =
0.5 s
这三者共同构成⼀个”不可能三⻆“。三者总体的表现会随着技术进步⽽越来越好。⼀款优秀的收集
器通常最多同时满⾜其中的两项。
简单来说,主要抓住两点:
- 吞吐量
- 暂停时间
在设计(或使⽤)GC算法时,必须确定我们的⽬标:
⼀个GC算法只可能针对两个⽬标之⼀(即只专注于 较⼤吞吐量或最⼩暂停时间),或尝试找⼀个⼆
者的折衷。
现在标准,在最⼤吞吐量优先的情况下,降低停顿时间
六、JVM调优原则
1. 优先原则
优先架构调优和代码调优,JVM优化是不得已的⼿段,⼤多数的Java应⽤不需要进⾏JVM优化
2. 堆设置
参数-Xms和-Xmx,通常设置为相同的值,避免运⾏时要不断扩展JVM内存,建议扩⼤⾄3-4倍
FullGC后的⽼年代空间占⽤。
3. 垃圾回收器设置
1> GC 发展阶段
Serial => Parallel(并⾏) => CMS(并发) => G1 => ZGC
截⽌jdk1.8 ,⼀共有7款不同垃圾收集器。
每⼀款不同的垃圾收集器都有不同的特点,在具体使⽤ 的时候,需要根据具体的情况选择不同的
垃圾回收器
垃圾收集器 | 分类 | 作用位置 | 使⽤算法 | 特点 | 使⽤场景 |
Serial | 串⾏ | 新⽣ 代 | 复制算法 | 响应速度 优先 | 适⽤于单CPU环境下client模式 |
ParNew | 并⾏ | 新⽣ 代 | 复制算法 | 响应速度 优先 | 多CPU环境下Server模式下与CMS配合使⽤ |
Parallel | 并⾏ | 新⽣ 代 | 复制算法 | 吞吐量优 先 | 适⽤于后台运算⽽不需要太多交 互的场景 |
Serial Old | 串⾏ | ⽼年 代 | 标记-压缩 | 响应速度 优先 | 适⽤于单CPU环境下client模式 |
Parallel Old | 并⾏ | ⽼年 代 | 标记-压缩 | 吞吐量优 先 | 适⽤于后台运算⽽不需要太多交 互的场景 |
CMS | 并发 | ⽼年 代 | 标记-清除 | 响应速度 优先 | 适⽤于互联⽹或B/S业务 |
G1 | 并发、 并⾏ | 新、 ⽼ | 复制;标记 | 响应速度 优先 | ⾯向服务端应⽤ |
两个垃圾回收器之间有连线表示它们可以搭配使⽤,可选的搭配⽅案如下:
2> G1的适用场景
- ⾯向服务端应⽤,针对具有⼤内存、多处理器的机器。(在普通⼤⼩的堆⾥表现并不惊喜)
- 最主要的应⽤是需要低GC延迟,并具有⼤堆的应⽤程序提供解决⽅案;
- 在堆⼤⼩约6GB或更⼤时,可预测的暂停时间可以低于0.5秒;
(G1通过每次只清理⼀部分⽽不是全部Region的增量式清理在保证每次GC停顿时间不会过⻓) 。
- ⽤来替换掉JDK1.5中的CMS收集器,以下情况,使⽤G1可能⽐CMS好
-
- 超过50% 的java堆被活动数据占⽤;
- 对象分配频率或年代提升频率变化很⼤;
- GC停顿时间过⻓(⼤于0.5⾄1秒)
- 从经验上来说,整体⽽⾔:
-
- ⼩内存应⽤上,CMS ⼤概率会优于 G1;
- ⼤内存应⽤上,G1 则很可能更胜⼀筹。
这个临界点⼤概是在 6~8G 之间(经验值)
3> 其他收集器适⽤场景
- 如果你想要最⼩化地使⽤内存和并⾏开销,请选择Serial Old(⽼年代) + Serial(年轻代)
- 如果你想要最⼤化应⽤程序的吞吐量,请选择Parallel Old(⽼年代) + Parallel(年轻代)
- 如果你想要最⼩化GC的中断或停顿时间,请选择CMS(⽼年代) + ParNew(年轻代)
4. 年轻代设置
参数-Xmn,1-1.5倍FullGC之后的⽼年代空间占⽤。
避免新⽣代设置过⼩,当新⽣代设置过⼩时,会产⽣两种⽐较明显的现象,⼀是minor GC次数频
繁,⼆是可能导致 minor GC对象直接进⼊⽼年代。当⽼年代内存不⾜时,会触发Full GC。
避免新⽣代设置过⼤,当新⽣代设置过⼤时,会带来两个问题:⼀是⽼年代变⼩,可能导致Full
GC频繁执⾏;⼆是 minor GC 执⾏回收的时间⼤幅度增加。
-Xmn ⾄于这个参数则是对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置,也就是说如果
通 过-Xmn来配置新⽣代的内存⼤⼩,那么-XX:newSize = -XX:MaxnewSize = -Xmn,虽然会很
⽅便, 但需要注意的是这个参数是在JDK1.4版本以后才使⽤的。
5. 年⽼代设置
注重低延迟的应用:
- 年⽼代使⽤并发收集器,所以其⼤⼩需要⼩⼼设置,⼀般要考虑并发会话率和会话持续时间等 ⼀些参数;
- 如果堆设置偏⼩,可能会造成内存碎⽚、⾼回收频率以及应⽤暂停;
- 如果堆设置偏⼤,则需要较⻓的收集时间。
吞吐量优先的应用:
⼀般吞吐量优先的应⽤都有⼀个很⼤的年轻代和⼀个较⼩的年⽼代。
原因是,这样可以尽可能回收 掉⼤部分短期对象,减少中期的对象,⽽年⽼代尽存放⻓期存活对
象
6. 方法区设置
基于jdk1.7版本,永久代:参数-XX:PermSize和-XX:MaxPermSize;
基于jdk1.8版本,元空间:参数 -XX:MetaspaceSize和-XX:MaxMetaspaceSize;
通常设置为相同的值,避免运⾏时要不断扩展,建议扩⼤⾄1.2-1.5倍FullGc后的永久带空间占⽤。
七、JVM调优步骤
1. 监控分析
分析GC⽇志及dump⽂件,判断是否需要优化,确定瓶颈问题点。
监控JVM的状况,可以使⽤jdk⾃带的命令或者第三⽅可视化⼯具查看其运⾏状态。
1.1. 如何⽣成GC⽇志
常⽤参数部分会详细讲解如何⽣成GC⽇志
1.2. 如何产⽣dump⽂件
1> JVM的配置⽂件中配置
JVM启动时增加两个参数:
#出现 OOME 时⽣成堆 dump:
-XX:+HeapDumpOnOutOfMemoryError#⽣成堆⽂件地址:
-XX:HeapDumpPath=/home/hadoop/dump/
package com.wclass.example;import java.util.ArrayList;import java.util.List;import java.util.Random;/** -Xmx100m -Xms100m -XX:+HeapDumpOnOutOfMemoryError -* XX:HeapDumpPath=/Users/hadoop/Desktop*/public class OOMTest {public static void main(String[] args) {List<Picture> list = new ArrayList<>();while (true){try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}list.add(new Picture(new Random().nextInt(1024 * 1024)));}}
}class Picture{private byte[] pixels;public Picture(int length){this.pixels = new byte[length];}}
2> jmap生成
发现程序异常前通过执⾏指令,直接⽣成当前JVM的dump⽂件,9257是指JVM的进程号
jmap -dump:file=⽂件名.dump [pid]jmap -dump:format=b,file=testmap.dump 9257
package com.wclass.example;import java.util.ArrayList;
import java.util.List;/*** -XX:+PrintCommandLineFlags */
public class GCUseTest {public static void main(String[] args) {List<byte[]> list = new ArrayList<>();while (true){byte[] arr = new byte[100];list.add(arr);try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
3> 第三⽅可视化⼯具⽣成
4> 知识小结
第⼀种方式是⼀种事后⽅式,需要等待当前JVM出现问题后才能⽣成dump⽂件,实时性不⾼;
第⼆种方式在执⾏时,JVM是暂停服务的,所以对线上的运⾏会产⽣影响。所以建议第⼀种⽅式。
2. 判断
如果各项参数设置合理,系统没有超时⽇志或异常信息出现,GC频率不⾼,GC耗时不⾼,那么没 有必要进⾏GC优化,如果GC时间超
过1-3秒,或者频繁GC,则必须优化。
遇到以下情况,就需要考虑进⾏JVM调优:
- 系统吞吐量与响应性能不⾼或下降;
- Heap内存(⽼年代)持续上涨达到设置的最⼤内存值;
- Full GC 次数频繁;
- GC 停顿时间过⻓(超过1秒);
- 应⽤出现OutOfMemory 等内存异常;
- 应⽤中有使⽤本地缓存且占⽤⼤量内存空间;
3. 确定目标
调优的最终⽬的都是为了应⽤程序使⽤最⼩的硬件消耗来承载更⼤的吞吐量或者低延迟。
jvm调优 主要是针对垃圾收集器的收集性能优化,减少GC的频率和Full GC的次数,
令运⾏在虚拟机上的应⽤能 够使⽤更少的内存、高吞吐量、低延迟。
下⾯列举⼀些JVM调优的量化⽬标参考实例,注意:不同应⽤的JVM调优量化⽬标是不⼀样的。
- 堆内存使⽤率 <= 70%;
- ⽼年代内存使⽤率<= 70%;
- avgpause <= 1秒;
- Full GC 次数0 或 avg pause interval >= 24⼩时 ;
4. 调整参数
调优⼀般是从满⾜程序的内存使⽤需求开始的,之后是时间延迟的要求,最后才是吞吐量的要求,
要基于这个步骤来不断优化,每⼀个步骤都是进⾏下⼀步的基础,不可逆⾏之。
5. 对⽐调优前后指标差异
6. 重复以上过程
7. 应⽤
找到合适的参数,先在单台服务器上试运⾏,然后将这些参数应⽤到所有服务器,并进⾏后续跟 踪。