JVM-05.JVM调优工具及调优方法

思维导图:点击查看思维导图.
前言:阅读此文章前,需要了解的知识
链接: JVM-03.对象创建与内存分配.
链接: JVM-04.垃圾回收机制看着一篇就够了.

1.jps工具

作用: 查看当前系统中有哪些JAVA进程
JVM-05.JVM调优工具及调优方法_第1张图片

2.jmap工具

来查看内存信息,实例个数以及占用内存大小
文件较长,可以输出为对应的文件,也可以直接查看
在这里插入图片描述
JVM-05.JVM调优工具及调优方法_第2张图片
num:序号
instances:实例数量
bytes:占用空间大小
class name:类名称,[C is a char[],[S is a short[],[I is a int[],[B is a byte[],[[I is a int[][]

查看堆内存占用情况(jmap -heap +进程id)
JVM-05.JVM调优工具及调优方法_第3张图片
JVM-05.JVM调优工具及调优方法_第4张图片
堆内存dump
生成堆dump文件,该文件可以使用各种JVM工具打开装载,例如 jvisualvm,查看堆dump信息,分析内存溢出原因。

注意: 生产环境谨慎使用,需要导出当时的堆区对象快照,期间会引起程序暂停

jmap ‐dump:format=b,file=xxx.hprof 33980

也可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)

  • -XX:+HeapDumpOnOutOfMemoryError
  • -XX:HeapDumpPath=./ (路径)
public class TestJVM {
    byte [] a = new byte[1024 *100]; // 100k 每次new该对象时占用100k
    // OOM时自动生成堆dump
    // -Xms5M -Xmx5M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\jvm.dump
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        while (true) {
            list.add(new TestJVM());
        }
    }
}

下面是jvisualvm装入的堆dump信息:
JVM-05.JVM调优工具及调优方法_第5张图片
可以从对象实例数大小进行分析(系统突然内存飙升/CPU飙升,查看是否有大量类生成)
从图中可以看出byte[]占堆内存 86%
JVM-05.JVM调优工具及调优方法_第6张图片
进入分析发现都属于TestJVM类

3. jstack工具

jstack加进程id查找死锁:


E:\>jps
12160 Jps
26960 TestJstackDeadLock
E:\>jstack 26960

JVM-05.JVM调优工具及调优方法_第7张图片
JVM-05.JVM调优工具及调优方法_第8张图片
jvisualvm也能自动检查死锁: 线程的堆dump,点击可以查看到和jstack一样的信息
JVM-05.JVM调优工具及调优方法_第9张图片
jstack找出占用cpu高的线程堆栈信息:
1.使用top 命令查看哪个进程占用 CPU 高
JVM-05.JVM调优工具及调优方法_第10张图片

2.使用 top -p 命令 精确到该进程,显示该JAVA进程的内存情况,例如 top -p 21919
JVM-05.JVM调优工具及调优方法_第11张图片
2.按H(大写 shift + h),获取该进程下所有线程的 CPU 情况
JVM-05.JVM调优工具及调优方法_第12张图片
3. 找到内存和 CPU 占用最高的线程tid,比如 21920
4. 转为十六进制得到 55a0 (jstack对应的 nid=0x55a0 ),此为线程id的十六进制表示
5. 执行 jstack 21919 |grep -A 10 55a0,得到线程堆栈信息中 55a0 这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙升的调用方法

4.jinfo

查看正在运行的Java应用程序的扩展参数
查看jvm的参数: jinfo -flags
JVM-05.JVM调优工具及调优方法_第13张图片
查看系统属性:jinfo -sysprops 34160
JVM-05.JVM调优工具及调优方法_第14张图片

5.jstat

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]
例如jstat -gc 23924 10000 10 每隔十秒运行一次,运行十次

注意:使用的 JDK 版本是 JDK8

垃圾回收统计
jstat -gc pid 是 最常用 的 jstat 命令,可以评估程序内存使用及GC压力整体情况
在这里插入图片描述

标识 含义 标识 含义
S0C 第一个幸存区大小 (KB) S1C 第二个幸存区大小
S0U 第一个幸存区已使用大小 S1U 第二个幸存区已使用大小
EC 伊甸园区大小 EU 伊甸园区已使用大小
OC 老年代大小 OU 老年代已使用大小
MC 方法区(元空间)大小 MU 方法区(元空间)已使用大小
CCSC 压缩类空间大小 CCSU 压缩类空间已使用大小
YGC 年轻代垃圾回收次数 YGCT 年轻代垃圾回收消耗时间(s)
FGC 老年代垃圾回收次数 FGCT 老年代垃圾回收消耗时间(s)
GCT 垃圾回收消耗总时间(s)

堆内存统计
在这里插入图片描述

标识 含义 标识 含义
NGCMN 新生代最小容量 NGCMX 新生代最大容量
NGC 当前新生代容量 S0C 第一个幸存区大小
S1C 第二个幸存区的大小 EC 伊甸园区的大小
OGCMN 老年代最小容量 OGCMX 老年代最大容量
OGC 当前老年代大小 OC 当前老年代大小
MCMN 最小元数据容量 MCMX 最大元数据容
MC 当前元数据空间大小 CCSMN 最小压缩类空间大小
CCSMX 最大压缩类空间大小 CCSC 当前压缩类空间大小
YGC 年轻代gc次数) FGC 老年代GC次数

新生代垃圾回收统计
在这里插入图片描述

标识 含义 标识 含义
S0C 第一个幸存区的大小 S1C 第二个幸存区的大小
S0U 第一个幸存区的使用大小 S1U 第二个幸存区的使用大小
TT 对象在新生代存活的次数 MTT 对象在新生代存活的最大次数
DSS 期望的幸存区大小 EC 伊甸园区的大小
EU 伊甸园区的使用大小 YGC 年轻代垃圾回收次数
YGCT 年轻代垃圾回收消耗时间

新生代内存统计
在这里插入图片描述

标识 含义 标识 含义
NGCMN 新生代最小容量 NGCMX 新生代最大容量
NGC 当前新生代容量 S0CMX 最大幸存1区大小
S0C 当前幸存1区大小 S1CMX 最大幸存2区大小
S1C 当前幸存2区大小 ECMX 最大伊甸园区大小
EC 当前伊甸园区大小 YGC 年轻代垃圾回收次数
FGC 老年代回收次数

老年代垃圾回收统计
在这里插入图片描述

标识 含义 标识 含义
MC 方法区大小 MU 方法区使用大小
CCSC 压缩类空间大小 CCSU 压缩类空间使用大小
OC 老年代大小 OU 老年代使用大小
YGC 年轻代垃圾回收次数 FGC 老年代垃圾回收次数
FGCT 老年代垃圾回收消耗时间 GCT 垃圾回收消耗总时间

老年代内存统计
在这里插入图片描述

标识 含义 标识 含义
OGCMN 老年代最小容量 OGCMX 老年代最大容量
OGC 当前老年代大小 OC 老年代大小
YGC 年轻代垃圾回收次数 FGC 老年代垃圾回收次数
FGCT 老年代垃圾回收消耗时间 GCT 垃圾回收消耗总时间

元空间统计
在这里插入图片描述

标识 含义 标识 含义
MCMN 最小元数据容量 MCMX 最大元数据容量
MC 当前元数据空间大小 CCSMN 最小压缩类空间大小
CCSMX 最大压缩类空间大小 CCSC 当前压缩类空间大小
YGC 年轻代垃圾回收次数 FGC 老年代垃圾回收次数
FGCT 老年代垃圾回收消耗时间 GCT 垃圾回收消耗总时间

垃圾回收信息统计(比例)
在这里插入图片描述

标识 含义 标识 含义
S0 幸存1区当前使用比例 S1 幸存2区当前使用比例
E 伊甸园区使用比例 O 老年代使用比例
M 元数据区使用比例 CCS 压缩使用比例
YGC 年轻代垃圾回收次数 FGC 老年代垃圾回收次数
FGCT 老年代垃圾回收消耗时间 GCT 垃圾回收消耗总时间

VM 最近编译情况
在这里插入图片描述

标识 含义 标识 含义
Compiled 最近编译方法的数量 Size 最近编译方法的字节码数量
Type 最近编译方法的编译类型 Method 方法名标识。

5.JVM运行情况分析

        在进行 JVM 调优之前,必须先获取JVM的运行情况。使用 jstat gc -pid 命令可以计算出一些至关重要的信息,有了这些信息就可以对 JVM 进行一些优化了,首先可以进行 JVM 参数的设置。例如:JVM堆内存大小,年轻代大小,Eden 和 Survivor 的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值等,在后期的测试中,也可以使用这些信息,定位问题之所在。

年轻代对象增长的速率
        可以执行命令 jstat -gc pid 1000 1000 (每隔1秒执行1次命令,共执行1000次),通过观察 EU(Eden区已使用)估算每秒 Eden 大概新增多少对象,如果系统负载不高,可以把频率1秒换成1分钟,甚至10分钟来观察整体情况。注意,一般系统可能有高峰期和日常期,所以需要在不同的时间分别估算不同情况下对象增长速率。

Young GC 的触发频率和每次耗时
        通过预估年轻代对象增长速率就能推根据 Eden 区的大小推算出 Young GC 触发频率,Young GC 的平均耗时可以通过 YGCT/YGC(年轻代垃圾回收消耗时间/年轻代垃圾回收次数)计算,由此我们就能知道大概多久系统会因为 Young GC 的执行而卡顿多久。

每次 Young GC 后有多少对象存活和进入老年代
        知道 Young GC 的触发频率后,假设是每两分钟触发一次 Young GC,那么就可以通过 jstat -gc pid 120000 10 ,观察每次执行后 Eden 区, survivor 区和老年代使用的变化情况。在每次 GC 后 Eden 区使用一般会大幅减少,survivor 区和老年代都有可能增长,这些增长的对象就是每次 Young GC 后存活的对象,同时还可以看出每次 Young GC 大概有多少对象进入老年代,推测出老年代增长速率

Full GC的触发频率和每次耗时
        通过老年代增长速率,就可以知道老年代触发 Full GC 的频率,Full GC的每次耗时可以通过 FGCT/FGC(老年代垃圾回收消耗时间/老年代垃圾回收次数)计算。

优化思路: 尽量让每次Young GC后的存活对象小于Survivor区域的50%,让用过即死的对象尽量都留存在年轻代里。尽量别让对象进入老年代。避免频繁 Full GC。

6.JVM 简单工具调优分析及步骤

JVM-05.JVM调优工具及调优方法_第15张图片

不使用额外工具是应该如何获取 JVM 运行信息然后进行调优呢?
第一步:jps 定位运行查询pid
第二步:jinfo -flags pid查看 JVM 运行参数信息
第三步:jstat -gc 3356 10000 10采集 GC 样本,由于 jstate 解析的 GC 使用情况是从项目启动时开始计算的,所以根据以上数据可以预估程序自启动到现在为止,期间发生Young GC 和 Full GC 的次数和耗时。

假设采集GC数据如下:

  • 系统配置:双核4G
  • JVM 内存大小:2G
  • 系统运行时间 7 天
  • 运行期间发生 Full GC 次数和耗时:500+ 次,200+ 秒
  • 运行期间发生 Young GC 次数和耗时:10000+ 次,500+ 秒

根据分析得出如下信息:

  • 平均每 20 分钟发生一次 Full GC,每次耗时大约 400 毫秒
  • 平均每 60 秒发生一次 Young GC,每次耗时大约 50 毫秒

第四步: 根据 JVM 参数及采集的 GC 信息绘制大致的内存对象流转模型图

假设样例中的 JVM 参数如下:

-Xms1536M -Xmx1536M -Xmn512M -Xss256K 
-XX:SurvivorRatio=6 -XX:MetaspaceSize=256M 
-XX:MaxMetaspaceSize=256M -XX:+UseParNewGC 
-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 
-XX:+UseCMSInitiatingOccupancyOnly

结合以上信息可以大概推断出:

堆内存空间 1.5 G ,年轻代空间 512M,方法区 256M,栈256K SurvivorRatio=6:Eden区,384M S0、S1各64M CMSInitiatingOccupancyFraction=75,UseCMSInitiatingOccupancyOnly:老年代空间达到 768M 触发 Full GC 平均每 20 分钟发生一次 Full GC:每 20 分钟有700多M对象被添加到老年代 平均每 60 秒发生一次 Young GC:每 60 秒 Eden 区满,假设程序运行平稳,即线程大概每秒产生 6M 左右的对象

JVM-05.JVM调优工具及调优方法_第16张图片
第五步: 根据对象流转规则,推理程序可能出现的问题
对象流转规则链接: JVM-03.对象创建与内存分配.
第六步: 根据可疑问题进行优化,优化完成进行测试

下面是一些常见的问题:

  • Full GC 和 Young GC频繁
    可能原因:如果是年轻代空间紧张,业务高峰期,大量对象被创建,年轻代空间被塞满,Survivor 区空间紧张,对象晋升阈值降低,导致生存周期很短的对象也会被复制到老年代,导致老年代频繁发生Full GC
    解决方案:增大新生代内存,使 Young GC 更少,并且 Survivor 区增大,对象晋升阈值上升,原本生命周期不长的对象不会进入老年代,从而减少老年代 Full GC发生
  • Full GC 频繁,甚至多于 Young GC
    可能原因:
    1.元空间不足导致的多余 Full GC
    2.显示调用System.gc()造成多余的Full GC,这种一般线上通过­XX:+DisableExplicitGC参数禁用,如果加上了这个JVM启动参数,那么代码中调用System.gc()将失效
    3.默认的老年代空间担保机制,可能使Full GC次数是Young GC次数的两倍。因为老年代空间担保机制使得 Young GC 时先判断老年代空间是否足够,如果不够,先进行一次Full GC。然后再进行Young GC,很有可能Young GC后,对象被挪入老年代又触发了-XX:CMSInitiatingOccupancyFraction = 75 配置的老年代 Full GC 比例,再次触发 Full GC,造成非常频繁的 Full GC,甚至是 Young GC 的两倍!
    4.大对象直接进入老年代(可能是很大一批对象),可以使用 jmap 或者 jvisualvm 等工具查看对象的实例个数,如果发现某个对象个数异常,问题很有可能就在这个对象上。此问题如何解决:分析下占用 CPU 较高的线程,一般有大量对象不断产生,对应的方法代码肯定会被频繁调用,占用的 CPU 必然较高(jstack 和 jvisualvm 可以定位问题代码的位置)
  • 请求高峰期发生Full GC,单次暂停时间特别长
    可能原因:如果使用CMS做老年代回收器,CMS做重新标记时会扫描整个堆内存,在业务高峰时,年轻代对象个数较多,扫描标记时间会变得非常多(需要根据对象找引用)
    解决方案:重新标记前对新生代对象先做一次垃圾清理,重新标记阶段需要扫描和标记的对象就会变短(-XX:+CMSScavengeBeforeRemark)
  • 老年代充裕的情况下,发生Full GC(CMS JDK1.7)
    可能原因:JDK1.7 之前使用的是永久代,永久代空间不足导致
    解决方案:增大永久代大小

7.内存泄漏与内存溢出

内存泄露:已申请的内存无法释放。比如:使用HashMap作为静态缓存对象,不断的往里面put数据,这些数据就会一直占用老年代的空间,这个map也可能会随着程序的运行不断的变大,时间一长就会导致频繁的 Full GC 甚至发生OOM。 这种情况完全可以考虑采用一些成熟的 JVM 级缓存框架来解决,比如 ehcache 等自带一些LRU数据淘汰算法的框架来作为JVM级的缓存。

内存溢出:无法申请到足够的内存。当需要存储的数据超出了指定空间的大小时数据就会发生越界。举例来说,常见的溢出,是指在栈空间里,分配了超过数组长度的数据,导致多出来的数据覆盖了栈空间其他位置的数据,这种情况发生时,可能会导致程序出现各种难排查的异常行为,或是被有心人利用,修改特定位置的变量数据达到溢出攻击的目的。而Java中的内存溢出,一般指OOM。

8.GC日志详解

在生产环境中我们一般不能使用jvisualvm等工具,但是可以配置一些参数置把程序运行过程中的 GC 日志全部打印出来,然后分析 GC 日志得到关键性指标,分析 GC 原因,调优JVM参数。 打印 GC 日志方法,在 JVM 参数里增加参数。如果是 Tomcat 直接添加在 JAVA_OPTS 变量里。

‐Xloggc:./gc‐%t.log ‐XX:+PrintGCDetails ‐XX:+PrintGCDateStamps ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCCause ‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100M
./ 打印在当前目录(可以指定路径) %t时间 PrintGCDateStamps日期 PrintGCTimeStamps时间戳 流动打印 保留最后打印的10个文件 每个100M作为一个文件

如何分析GC日志
下图是 JVM 刚刚启动时打印的一段 JDK1.8默认Parallel + ParallelOld的GC 日志:

从日志可以前面的几次 Full GC 都是由于元空间不够导致的,所以我们一般将元空间初始值和最大值设置一样,这样可以减少 Full GC 导致的项目启动过慢。
‐Xloggc:./gc‐adjust‐%t.log ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:+PrintGCDetails ‐XX:+Print GCDateStamps ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCCause ‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100M

不同的垃圾回收器,打印的 GC 日志也会不同,通过打印 GC 日志,可以发现日志中的垃圾回收步骤和我们上篇文章介绍的不同垃圾回收器回收过程对应(如初始标记->并发标记->重新标记->并发清理->并发重置或使用SerialOld)。
CMS日志

CMS 打印日志设置的参数
‐Xloggc:d:/gc‐cms‐%t.log ‐Xms50M ‐Xmx50M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:+PrintGCDetails ‐XX:+P rintGCDateStamps ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCCause ‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100M ‐XX:+UseParNewGC ‐XX:+UseConcMarkSweepGC

G1 打印日志设置的参数
‐Xloggc:d:/gc‐g1‐%t.log ‐Xms50M ‐Xmx50M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:+PrintGCDetails ‐XX:+Pr intGCDateStamps‐XX:+PrintGCTimeStamps ‐XX:+PrintGCCause ‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100M ‐XX:+UseG1GC

在生产中我们一般都会通过一些工具区自动化的解析 GC 日志。比如: gceasy,可以 上传gc文件,然后他会利用可视化的界面来展现GC情况。具体下图所示:
JVM-05.JVM调优工具及调优方法_第17张图片
由图可以看出年轻代,老年代,以及永久代的内存分配,和最大使用情况。
JVM-05.JVM调优工具及调优方法_第18张图片
堆内存在GC之前和之后的变化,以及其他信息
甚至还会提供一些JVM的优化建议:
JVM-05.JVM调优工具及调优方法_第19张图片
最后附上查看JVM参数汇总的命令:
java -XX:+PrintFlagsInitial 打印所有参数选项的默认值
java -XX:+PrintFlagsFinal 打印所有参数选项在运行程序时生效的值

9.阿里巴巴Arthas

在生产上一般使用一些开源的工具,比如Arthas(阿尔萨斯)、Prometheus(普罗米修斯)等。Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6+, 采用命令行交互模式,可以方便的定位和诊断线上程序运行问题。Arthas 官方文档:链接: https://alibaba.github.io/arthas.

你可能感兴趣的:(性能调优,JVM,jvm,java,jar)