目录
1. 概述篇
1.1. 背景说明
1.3. 调优概述
1.4. 性能优化的步骤
2. JVM 监控及诊断工具-命令行篇
2.1. 概述
2.2. jps:查看正在运行的 Java 进程
2.3. jstat:查看 JVM 统计信息
2.3.1 使用方法
2.3.2 实战 如何预估JVM运行情况
2.3.3系统频繁Full GC导致系统卡顿怎么处理
2.4. jinfo:实时查看和修改 JVM 配置参数
2.5. jmap:导出内存映像文件&内存使用情况
2.6. jhat:JDK 自带堆分析工具
2.7. jstack:打印 JVM 中线程快照
2.7.1 实战1:找到死锁的线程
2.7.2 实战2:CPU突然飙高,该如何定位代码
2.8. jcmd:多功能命令行
3. JVM 监控及诊断工具-GUI 篇
3.1. 工具概述
3.2. JConsole
3.3. Visual VM
3.4. Eclipse MAT
3.5. JProfiler
3.6. Arthas
3.7. Java Misssion Control
3.8. 其他工具
4. JVM 运行时参数
4.1. JVM 参数选项
4.1.1. 类型一:标准参数选项
4.1.2. 类型二:-X 参数选项
4.1.3. 类型三:-XX 参数选项
4.2. 添加 JVM 参数选项
4.3. 常用的 JVM 参数选项
4.3.1. 打印设置的 XX 选项及值
4.3.2. 堆、栈、方法区等内存大小设置
4.3.3. OutOfMemory 相关的选项
4.3.4. 垃圾收集器相关选项
4.3.5. GC 日志相关选项
4.3.6. 其他参数
4.4. 通过 Java 代码获取 JVM 参数
5. 分析 GC 日志
5.1. GC 分类
5.2. GC 日志分类
1. 背景说明
我们学习JVM的最终目的都是为了进行JVM调优,这既是工作的需要,也是很多面试官资环考察的问题,例如生产环境中的问题有:
-
生产环境发生了内存溢出该如何处理/p>
生产环境应该给服务器分配多少内存合适/p>
如何对垃圾回收器的性能进行调优/p>
生产环境 CPU 负载飙高该如何处理/p>
生产环境应该给应用分配多少线程合适/p>
不加 log,如何确定请求是否执行了某一行代码/p>
不加 log,如何实时查看某个方法的入参与返回值/p>
2. JVM 监控及诊断工具-命令行篇
2.1. 概述
性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。
Java 作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成 Java 应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、 络 I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。
体会 1:使用数据说明问题,使用知识分析问题,使用工具处理问题。
体会 2:无监控、不调优!
简单命令行工具
在我们刚接触 java 学习的时候,大家肯定最先了解的两个命令就是 javac,java,那么除此之外,还有没有其他的命令可以供我们使用呢/p>
我们进入到安装 jdk 的 bin 目录,发现还有一系列辅助工具。这些辅助工具用来获取目标 JVM 不同方面、不同层次的信息,帮助开发人员很好地解决 Java 应用程序的一些疑难杂症。
官方源码地址:jdk/jdk11: 1ddf9a99e4ad /src/jdk.jcmd/share/classes/sun/tools/
2.2. jps:查看正在运行的 Java 进程
jps(Java Process Status):显示指定系统内所有的 HotSpot 虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。
说明:对于本地虚拟机进程来说,进程的本地虚拟机 ID 与操作系统的进程 ID 是一致的,是唯一的。
基本使用语法为:jps [options] [hostid]
我们还可以通过追加参数,来打印额外的信息。
options 参数
-
-q:仅仅显示 LVMID(local virtual machine id),即本地虚拟机唯一 id。不显示主类的名称等
-
-l:输出应用程序主类的全类名 或 如果进程执行的是 jar 包,则输出 jar 完整路径
-
-m:输出虚拟机进程启动时传递给主类 main()的参数
-
-v:列出虚拟机进程启动时的 JVM 参数。比如:-Xms20m -Xmx50m 是启动程序指定的 jvm 参数。
说明:以上参数可以综合使用。
补充:如果某 Java 进程关闭了默认开启的 UsePerfData 参数(即使用参数-XX:-UsePerfData),那么 jps 命令(以及下面介绍的 jstat)将无法探知该 Java 进程。
hostid 参数
RMI 注册表中注册的主机名。如果想要远程监控主机上的 java 程序,需要安装 jstatd。
对于具有更严格的安全实践的 络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或 络的访问,尽管这种技术容易受到 IP 地址欺诈攻击。
如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行 jstatd 服务器,而是在本地使用 jstat 和 jps 工具。
2.3. jstat:查看 JVM 统计信息
jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。
2.3.1 使用方法
官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
基本使用语法为:jstat -option [-t] [-hlines] vmid [interval [count]]
查看命令相关参数:jstat-h 或 jstat-help
其中 vmid 是进程 id ,也就是 jps 之后看到的前面的 码,如下:
例如,在本人公司某个测试机下的进程如下所示:
option 参数
选项 option 可以由以下值构成。
类装载相关的:
-
-class:显示 ClassLoader 的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
垃圾回收相关的:
-
-gc:显示与 GC 相关的堆信息。包括 Eden 区、两个 Survivor 区、老年代、永久代等的容量、已用空间、GC 时间合计等信息。
-
-gccapacity:显示内容与-gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间。
-
-gcutil:显示内容与-gc 基本相同,但输出主要关注已使用空间占总空间的百分比。
-
-gccause:与-gcutil 功能一样,但是会额外输出导致最后一次或当前正在发生的 GC 产生的原因。
-
-gcnew:显示新生代 GC 状况
-
-gcnewcapacity:显示内容与-gcnew 基本相同,输出主要关注使用到的最大、最小空间
-
-geold:显示老年代 GC 状况
-
-gcoldcapacity:显示内容与-gcold 基本相同,输出主要关注使用到的最大、最小空间
-
-gcpermcapacity:显示永久代使用到的最大、最小空间。
JIT 相关的:
-
-compiler:显示 JIT 编译器编译过的方法、耗时等信息
-
-printcompilation:输出已经被 JIT 编译的方法
每个字段的具体含义如下:
表头 | 含义(字节) |
---|---|
EC | Eden 区的大小 |
EU | Eden 区已使用的大小 |
S0C | 幸存者 0 区的大小 |
S1C | 幸存者 1 区的大小 |
S0U | 幸存者 0 区已使用的大小 |
S1U | 幸存者 1 区已使用的大小 |
MC | 元空间的大小 |
MU | 元空间已使用的大小 |
OC | 老年代的大小 |
OU | 老年代已使用的大小 |
CCSC | 压缩类空间的大小 |
CCSU | 压缩类空间已使用的大小 |
YGC | 从应用程序启动到采样时 young gc 的次数 |
YGCT | 从应用程序启动到采样时 young gc 消耗时间(秒) |
FGC | 从应用程序启动到采样时 full gc 的次数 |
FGCT | 从应用程序启动到采样时的 full gc 的消耗时间(秒) |
GCT | 从应用程序启动到采样时 gc 的总时间 |
除此之外,我们还可以更细致的查看各个区域的状态。
堆内存统计
具体含义是:
-
N GC MN:新生代最小容量
-
N GC MX:新生代最大容量
-
NGC:当前新生代容量
-
S0C:第一个幸存区大小
-
S1C:第二个幸存区的大小
-
EC:伊甸园区的大小
-
O GC MN:老年代最小容量
-
OGC MX:老年代最大容量
-
OGC:当前老年代大小
-
OC:当前老年代大小
-
MCMN:最小元数据容量
-
MCMX:最大元数据容量
-
MC:当前元数据空间大小
-
CCSMN:最小压缩类空间大小
-
CCSMX:最大压缩类空间大小
-
CCSC:当前压缩类空间大小
-
YGC:年轻代gc次数
-
FGC:老年代GC次数
新生代垃圾回收统计
-
S0C:第一个幸存区的大小
-
S1C:第二个幸存区的大小
-
S0U:第一个幸存区的使用大小
-
S1U:第二个幸存区的使用大小
-
TT:对象在新生代存活的次数
-
MTT:对象在新生代存活的最大次数
-
DSS:期望的幸存区大小
-
EC:伊甸园区的大小
-
EU:伊甸园区的使用大小
-
YGC:年轻代垃圾回收次数
-
YGCT:年轻代垃圾回收消耗时间
除此之外还有新生代内存统计-gcnewcapacity、老年代垃圾回收统计-gcold、老年代内存统计-gcoldcapacity、元数据空间统计-gcmetacapacity等等,可以看到对应区域更细致的信息。
2.3.2 实战 如何预估JVM运行情况
用 jstat gc -pid 命令可以计算出如下一些关键数据,有了这些数据就可以采用之前介绍过的优化思路,先给自己的系统设置一些初始性的 JVM参数,比如堆内存大小,年轻代大小,Eden和Survivor的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值等。
年轻代对象增长的速率 可以执行命令 jstat -gc pid 1000 10 (每隔1秒执行1次命令,共执行10次,mac环境下需要去掉命令里的pid),通过观察EU(eden区的使用)来估算每秒eden大概新增多少对象,如果系统负载不高,可以把频率1秒换成1分钟,甚至10分钟来观察整体情况。注意,一般系统可能有高峰期和日常期,所以需要在不同的时间分别估算不同情况下对象增长速率。
Young GC的触发频率和每次耗时 知道年轻代对象增长速率我们就能推根据eden区的大小推算出Young GC大概多久触发一次,Young GC的平均耗时可以通过 YGCT/YGC 公式算出,根据结果我们大概就能知道系统大概多久会因为Young GC的执行而卡顿多久。
每次Young GC后有多少对象存活和进入老年代 这个因为之前已经大概知道Young GC的频率,假设是每5分钟一次,那么可以执行命令 jstat -gc pid 300000 10 ,观察每次结果eden, survivor和老年代使用的变化情况,在每次gc后eden区使用一般会大幅减少,survivor和老年代都有可能增长,这些增长的对象就是每次 Young GC后存活的对象,同时还可以看出每次Young GC后进去老年代大概多少对象,从而可以推算出老年代对象增长速率。
Full GC的触发频率和每次耗时 知道了老年代对象的增长速率就可以推算出Full GC的触发频率了,Full GC的每次耗时可以用公式 FGCT/FGC 计算得出。
优化思路其实简单来说就是尽量让每次Young GC后的存活对象小于Survivor区域的50%,都留存在年轻代里。尽量别让对象进入老年 代。尽量减少Full GC的频率,避免频繁Full GC对JVM性能的影响。
2.3.3系统频繁Full GC导致系统卡顿怎么处理
我们可以推测下full gc比minor gc还多的原因有哪些1、元空间不够导致的多余full gc。 2、显示调用System.gc()造成多余的full gc,这种一般线上尽量通过-XX:+DisableExplicitGC参数禁用,如果加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果。 3、过多的对象进入老年代。
一般来说每次Full GC会在500毫秒左右,而Young GC每次都在50ms以内。图中数据是我们做的一个假定:
此时我们想到两种策略:加大老年代,还是加大新生代/p>
结合这个对象挪动到老年代的规则推理下,我们可以判断,如果加大了老年代空间,此时老年代执行full GC的周期肯定会变长,但是每次执行的full gc耗费的时间更长了。
而如果我们加大新生代,此时会有更多的对象熬不过S0和S1,因此会有更少的对象进入老年代,因此Full GC的频率必然低了,能解决问题。
2.4. jinfo:实时查看和修改 JVM 配置参数
jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。在很多情况卡,Java 应用程序不会指定所有的 Java 虚拟机参数。而此时,开发人员可能不知道某一个具体的 Java 虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了 jinfo 工具,开发人员可以很方便地找到 Java 虚拟机参数的当前值。
基本使用语法为:jinfo [options] pid
说明:java 进程 ID 必须要加上
选项 | 选项说明 |
---|---|
no option | 输出全部的参数和系统属性 |
-flag name | 输出对应名称的参数 |
-flag [+-]name | 开启或者关闭对应名称的参数 只有被标记为 manageable 的参数才可以被动态修改 |
-flag name=value | 设定对应名称的参数 |
-flags | 输出全部的参数 |
-sysprops | 输出系统属性 |
jinfo -sysprops
例如,在本人公司某个测试机下的进程如下所示:
然后执行如下命令:
可以看到很多基础信息:
这里包含了服务的大量基础信息
我们还可以使用jinfo -flags命令查看服务启动的详细配置信息
这些信息太多了,我们可以根据需要查找特定的几个,例如:
使用jinfo -flag查看某个特定项的设置,例如
2.5. jmap:导出内存映像文件&内存使用情况
jmap(JVM Memory Map):作用一方面是获取 dump 文件(堆转储快照文件,二进制文件),它还可以获取目标 Java 进程的内存相关信息,包括 Java 堆各区域的使用情况、堆中对象的统计信息、类加载信息等。开发人员可以在控制台中输入命令“jmap -help”查阅 jmap 工具的具体使用方式和一些标准选项配置。
官方帮助文档:jmap
基本使用语法为:
-
jmap [option] pid
-
jmap [option] executable core
-
jmap [option] [server_id@] remote server IP or hostname
选项 | 作用 |
---|---|
-dump | 生成 dump 文件(Java 堆转储快照),-dump:live 只保存堆中的存活对象 |
-heap | 输出整个堆空间的详细信息,包括 GC 的使用、堆配置信息,以及内存的使用信息等 |
-histo | 输出堆空间中对象的统计信息,包括类、实例数量和合计容量,-histo:live 只统计堆中的存活对象 |
-J flag | 传递参数给 jmap 启动的 jvm |
-finalizerinfo | 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象,仅 linux/solaris 平台有效 |
-permstat | 以 ClassLoader 为统计口径输出永久代的内存状态信息,仅 linux/solaris 平台有效 |
-F | 当虚拟机进程对-dump 选项没有任何响应时,强制执行生成 dump 文件,仅 linux/solaris 平台有效 |
说明:这些参数和 linux 下输入显示的命令多少会有不同,包括也受 jdk 版本的影响。
案例1:查看加载的类信息
输出的内容如下:
其中:
案例2:查看内存信息
格式:
上面这种方式在mac下是被禁止的无法使用。另一种格式是:
这样我们就将对信息复制到eureka.hprof里去了。
也可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)
例如,我司某个服务的参数配置如下:
由于 jmap 将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap 需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由 jmap 导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。
举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:live 选项将无法探知到这些对象。
另外,如果某个线程长时间无法跑到安全点,jmap 将一直等下去。与前面讲的 jstat 则不同,垃圾回收器会主动将 jstat 所需要的摘要数据保存至固定位置之中,而 jstat 只需直接读取即可。
2.6. jhat:JDK 自带堆分析工具
jhat(JVM Heap Analysis Tool):Sun JDK 提供的 jhat 命令与 jmap 命令搭配使用,用于分析 jmap 生成的 heap dump 文件(堆转储快照)。jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。
使用了 jhat 命令,就启动了一个 http 服务,端口是 7000,即 http://localhost:7000/,就可以在浏览器里分析。
说明:jhat 命令在 JDK9、JDK10 中已经被删除,官方建议用 VisualVM 代替。
基本适用语法:jhat option dumpfile
option 参数 | 作用 |
---|---|
-stack false | true | 关闭|打开对象分配调用栈跟踪 |
-refs false | true | 关闭|打开对象引用跟踪 |
-port port-number | 设置 jhat HTTP Server 的端口 ,默认 7000 |
-exclude exclude-file | 执行对象查询时需要排除的数据成员 |
-baseline exclude-file | 指定一个基准堆转储 |
-debug int | 设置 debug 级别 |
-version | 启动后显示版本信息就退出 |
-J flag | 传入启动参数,比如-J-Xmx512m |
2.7. jstack:打印 JVM 中线程快照
jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。
生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用 jstack 显示各个线程调用的堆栈情况。
官方帮助文档:jstack
在 thread dump 中,要留意下面几种状态
-
死锁,Deadlock(重点关注)
-
等待资源,Waiting on condition(重点关注)
-
等待获取监视器,Waiting on monitor entry(重点关注)
-
阻塞,Blocked(重点关注)
-
执行中,Runnable
-
暂停,Suspended
-
对象等待中,Object.wait() 或 TIMED_WAITING
-
停止,Parked
option 参数 | 作用 |
---|---|
-F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用本地方法的话,可以显示 C/C++的堆栈 |
2.7.1 实战1:找到死锁的线程
如何发生了死锁,我们该如何定位到哪里出现问题了呢如下面这个代码执行时就会死锁:
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!