JVM调优
1、JVM性能调优指标熟悉哪些?
JVM调优目标:使用较小的内存占用来获得较高的吞吐量或者较低的延迟。
程序在上线前的测试或运行中有时会出现一些大大小小的JVM
问题,比如cpu load
过高、请求延迟、tps
降低等,甚至出现内存泄漏(每次垃圾收集使用的时间越来越长,垃圾收集频率越来越高,每次垃圾收集清理掉的垃圾数据越来越少)、内存溢出导致系统崩溃,因此需要对JVM
进行调优,使得程序在正常运行的前提下,获得更高的用户体验和运行效率。这里有几个比较重要的指标:
【内存占用】:程序正常运行需要的内存大小。
【延迟】:由于垃圾收集而引起的程序停顿时间。
【吞吐量】:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。
当然,和CAP
原则一样,同时满足一个程序内存占用小、延迟低、高吞吐量是不可能的,程序的目标不同,调优时所考虑的方向也不同,在调优之前,必须要结合实际场景,有明确的的优化目标,找到性能瓶颈,对瓶颈有针对性的优化,最后进行测试,通过各种监控工具确认调优后的结果是否符合目标。
2、JVM调优经验总结?
JVM
配置方面,一般情况可以先用默认配置(基本的一些初始参数可以保证一般的应用跑的比较稳定了),在测试中根据系统运行状况(会话并发情况、会话时间等),结合gc
日志、内存监控、使用的垃圾收集器等进行合理的调整,当老年代内存过小时可能引起频繁Full GC
,当内存过大时Full GC
时间会特别长。
那么JVM
的配置比如新生代、老年代应该配置多大最合适呢?
答案是不一定,调优就是找答案的过程,物理内存一定的情况下,新生代设置越大,老年代就越小,Full GC
频率就越高,但Full GC
时间越短。相反新生代设置越小,老年代就越大,Full GC
频率就越低,但每次Full GC
消耗的时间越大。
【建议如下】:
1)、-Xms(初始堆内存大小)
和-Xmx(最大堆内存大小)
的值设置成相等,堆大小默认为-Xms
指定的大小,默认空闲堆内存小于40%
时,JVM会扩大堆到-Xmx
指定的大小。空闲堆内存大于70%
时,JVM会减小堆到-Xms
指定的大小。如果在Full GC
后满足不了内存需求会动态调整,这个阶段比较耗费资源。
2)、新生代尽量设置大一些,让对象在新生代多存活一段时间,每次Minor GC
都要尽可能多的收集垃圾对象,防止或延迟对象进入老年代的机会,以减少应用程序发生Full GC
的频率。
3)、老年代如果使用CMS
收集器,新生代可以不用太大,因为CMS
的并行收集速度也很快,收集过程比较耗时的并发标记和并发清除阶段都可以与用户线程并发执行。
4)、方法区大小的设置,JDK1.6
之前需要考虑系统运行时动态增加的常量、静态变量等,JDK1.7
只要差不多能装下启动时和后期动态加载的类信息就行。
5)、除了JVM
配置可能存在问题,代码实现方面也可能存在问题,程序等待、内存泄漏:
a)、避免创建过大的对象及数组,过大的对象或数组在新生代没有足够空间容纳时会直接进入老年代,如果是短命的大对象,会提前出发Full GC
。
b)、避免同时加载大量数据,如一次从数据库中取出大量数据,或者一次从Excel中读取大量记录,可以分批读取,用完尽快清空引用。
c)、当集合中有对象的引用,这些对象使用完之后要尽快把集合中的引用清空,这些无用对象尽快回收避免进入老年代。
d)、可以在合适的场景(如实现缓存)采用软引用、弱引用,比如用软引用来为ObjectA
分配实例,SoftReference objectA=new SoftReference();
在发生内存溢出前,会将objectA
列入回收范围进行二次回收,如果这次回收还没有足够内存,才会抛出内存溢出的异常。
e)、避免产生死循环,产生死循环后,循环体内可能重复产生大量实例,导致内存空间被迅速占满。
f)、尽量避免长时间等待外部资源(数据库、网络、设备资源等)的情况,缩小对象的生命周期,避免进入老年代,如果不能及时返回结果可以适当采用异步处理的方式等。
3、JVM性能调优参数熟悉哪些?
JVM
性能调优参数是用于调整Java
虚拟机运行性能的关键参数。以下是一些常见的JVM
性能调优参数:
1)、堆内存大小调整:
例如:-Xms512m -Xmx1024m
。
-Xms
:初始堆内存大小。
-Xmx
:最大堆内存大小。
2)、新生代和老年代大小调整:
例如:-Xmn256m
。
-Xmn
:新生代大小。
通过-XX:NewRatio
调整新生代和老年代的比例,例如:-XX:NewRatio=3
,表示年轻代与年老代比值为1:3
。
3)、垃圾回收器选择与参数调整:
-XX:+UseParallelGC
:启用并行垃圾回收器。
-XX:+UseConcMarkSweepGC
:启用CMS
垃圾回收器。
-XX:+UseG1GC
:启用G1
垃圾回收器。针对服务端应用,G1 GC
提供了低延迟的目标。
4)、垃圾回收相关参数:
-XX:MaxGCPauseMillis
:设置GC
暂停时间目标。例如,设置为100
表示目标GC
暂停时间为100
毫秒。
-XX:ParallelGCThreads
:并行垃圾回收器使用的线程数,通常设置为逻辑CPU
核数。
5)、压缩指针相关参数:
-XX:+UseCompressedOops
:启用压缩指针,减少32
位系统上的对象指针大小。
6)、JIT编译器相关参数:
-XX:+UseCompressedClassPointers
:启用压缩类指针,减少32
位系统上的类指针大小。
-XX:+TieredCompilation
:启用分层编译,提高JIT编译器的效率。
7)、其他常用参数:
-XX:+HeapDumpOnOutOfMemoryError
:在发生OutOfMemoryError
时生成堆转储。
-XX:+PrintGCDetails
:打印详细的GC
日志,用于分析垃圾回收性能。
-XX:+PrintGCDateStamps
:在GC
日志中添加时间戳,便于日志分析。
8)、垃圾回收日志分析工具:
使用工具如VisualVM
、JConsole
、MAT (Memory Analyzer Tool)
等来分析垃圾回收日志,找出内存泄漏、频繁GC
等问题。
9)、JVM性能分析工具:
使用工具如JProfiler
、YourKit
等来进行JVM
性能分析,找出性能瓶颈并进行调优。
10)、线程参数:
如-XX:ThreadStackSize
设定线程栈大小等,确保线程的稳定运行。
不显式设置-Xss
或-XX:ThreadStackSize
时,在Linux x64
上ThreadStackSize
的默认值就是1024KB
,给Java
线程创建栈会用这个参数指定的大小。如果把-Xss
或者-XX:ThreadStackSize
设为0
,就是使用系统默认值。而在Linux x64
上HotSpot VM
给Java栈定义的系统默认大小也是1MB
。
在进行JVM
性能调优时,需要根据应用程序的具体需求和运行环境进行参数调整和测试,以达到最佳的性能表现。同时,也需要定期监控和分析应用程序的运行状态和性能数据,及时发现和解决潜在的问题。
4、线上CPU 100%怎么排查?
【1、排查CPU故障的常用命令】
1)、top
:Linux命令,可以实时查看各个进程的CPU使用情况,也可以查看最近一段时间的CPU使用情况,默认按CPU使用率排序。
2)、ps
:Linux命令,强大的进程状态监控命令,可以查看进程以及进程中线程的当前CPU使用情况,属于当前状态的采样数据。
3)、jstack
:Java提供的命令,可以查看某个进程的当前线程栈运行情况,根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。
4)、pstack
:Linux命令,可以查看某个进程的当前线程栈运行情况。
5)、jstat
:Java提供的命令,可以查看某个JVM进程的gc信息。
【2、应用负载高的时候怎么办】
一个应用占用CPU
很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环,CPU负载过高解决问题过程:
1)、使用【top】|【top -c】
命令查看系统资源占用信息定位异常进程,可发现PID为99737的CPU和内存占用率都非常高。也可以直接通过【jps -v】
找到对应的pid。键入大写的P按CPU使用率排序。
备注:top命令默认每3秒刷新一次,可以通过top -d <刷新时间间隔>
来指定刷新频率,如top -d 0.1
或top -d 0.01
等。top执行时,也可以按s键,修改时间间隔。
2)、使用【top -Hp PID】
查看该PID对应进程下各个线程的CPU使用情况。
PID(Process Identification)
操作系统里指进程识别号,也就是进程标识符。操作系统里每打开一个程序都会创建一个进程ID,即PID。PID是各进程的代号,每个进程有唯一的PID编号。它是进程运行时系统分配的,并不代表专门的进程。在运行时PID是不会改变标识符的,但是进程终止后PID标识符就会被系统回收,就可能会被继续分配给新运行的程序。
也可以通过【ps -mp pid -o THREAD,tid,time| sort -rn】
命令查看这个程序的线程信息,tid代表线程ID,time代表这个线程的已运行时间。
3)、使用【printf "%x\n" 线程号】
将异常线程号转化为16进制,为等会在jstack中查找方便。
printf "%x\n" 99738 => 1859a
4)、使用【jstack 进程号|grep 16进制异常线程号 -A90|-C10】
来定位异常代码的位置(最后的-A90是日志行数,-C10显示前后各10行数据,也可以输出为文本文件或使用其他数字),可以看到异常代码的位置。jstack 99737 | grep 1859a -A90|-C10 --color
。
找到相应代码检查,发现确实有死循环存在。
【3、什么场景会造成CPU低而负载却很高呢?】
负载总结为一句话就是:需要运行处理但又必须等待队列前的进程处理完成的进程个数。具体来说,也就是如下两种情况:
1)、等待被授权予CPU运行权限的进程。
2)、等待磁盘I/O完成的进程。
CPU低而负载高也就是说等待磁盘I/O完成的进程过多,就会导致队列长度过大,这样就体现到负载过大了,但实际是此时CPU被分配去执行别的任务或空闲,具体场景有如下几种:
1)、数据库抖动,造成线程队列夯住,负载升高。
2)、磁盘读写请求过多就会导致大量I/O等待。
CPU的工作效率要高于磁盘,而进程在CPU上面运行需要访问磁盘文件,这个时候CPU会向内核发起调用文件的请求,让内核去磁盘取文件,这个时候会切换到其他进程或者空闲,这个任务就会转换为不可中断睡眠状态。当这种读写请求过多就会导致不可中断睡眠状态的进程过多,从而导致负载高,CPU低的情况。
3)、外接硬盘故障,常见有挂了NFS,但是NFS server故障。
比如系统挂载了外接硬盘如NFS共享存储,经常会有大量的读写请求去访问NFS存储的文件,如果这个时候 NFS Server故障,那么就会导致进程读写请求一直获取不到资源,从而进程一直是不可中断状态,造成负载很高。
【4、监控发现线上机器内存占用率居高不下,如何分析】
1)、使用top -p pid
针对所要查的pid查看该进程的CPU和内存以及负载情况。
2)、jmap -histo:live [pid]
,然后分析具体的对象数目和占用内存大小,从而定位代码。
3)、jmap -dump:live,format=b,file=xxx.xxx [pid]
,然后利用MAT工具分析是否存在内存泄漏等等。
4)、jmap -heap pid
,查看jvm内存使用情况。
5)、jstat -gcutil pid 1000
,查看GC信息,1000代表每隔1000毫秒去查询。
6)、jinfo pid
命令查询启动参数。
【5、导致cpu飚高系统反应慢的原因有多个】
1)、上下文切换过多,对于cpu来说,同一时刻每个cpu核心只能运行一个线程,如果有多个线程要执行,cpu只能通过上下文切换的方式来执行不同的线程。在上下文切换中,i要保存运行线程的执行状态,ii让处于等待中的线程执行。这些过程需要cpu执行内核相关指令实现状态保存,如果较多的上下文切换会占据大量的cpu资源,从而使得cpu无法去执行用户进程中的指令,导致响应速度下降。例如:在java中,文件IO、网络IO、锁等待、线程阻塞等操作都会造成线程阻塞从而触发上下文切换。
2)、CPU资源过度消耗,也就是在程序中创建了大量的线程,或者有线程一直占用CPU资源无法释放,比如死循环这种,CPU利用率过高之后,导致应用中的线程无法获得CPU的调度,从而影响程序的执行效率。
5、几种常见的JVM调优场景?
【1、cpu占用过高】
cpu占用过高要分情况讨论,是不是业务上在搞活动,突然有大批的流量进来,而且活动结束后cpu占用率就下降了,如果是这种情况其实可以不用太关心,因为请求越多,需要处理的线程数越多,这是正常的现象。话说回来,如果你的服务器配置本身就差,cpu也只有一个核心,这种情况,稍微多一点流量就真的能够把你的cpu资源耗尽,这时应该考虑先把配置提升吧。
第二种情况,cpu占用率长期过高,这种情况下可能是你的程序有那种循环次数超级多的代码,甚至是出现死循环了,使用上面的步骤进行排查。
【2、死锁】
死锁并没有第一种场景那么明显,web应用肯定是多线程的程序,它服务于多个请求,程序发生死锁后,死锁的线程处于等待状态(WAITING
或TIMED_WAITING
),等待状态的线程不占用cpu,消耗的内存也很有限,而表现上可能是请求没法进行,最后超时了。在死锁情况不多的时候,这种情况不容易被发现。可以使用jstack
工具来查看,jstat -gcutil pid 1000
。
【3、内存泄漏】
我们都知道,java和c++的最大区别是前者会自动收回不再使用的内存,后者需要程序员手动释放。在c++中,如果我们忘记释放内存就会发生内存泄漏。但是,不要以为jvm帮我们回收了内存就不会出现内存泄漏。程序发生内存泄漏后,进程的可用内存会慢慢变少,最后的结果就是抛出OOM错误。发生OOM错误后可能会想到是内存不够大,于是把-Xmx参数调大,然后重启应用。这么做的结果就是,过了一段时间后,OOM依然会出现。最后无法再调大最大堆内存了,结果就是只能每隔一段时间重启一下应用。
内存泄漏的另一个可能的表现是请求的响应时间变长了,这是因为频繁发生的GC会暂停其它所有线程(Stop The World)造成的。现象是虽然一直在gc,但占用的内存却越来越多,说明程序有的对象无法被回收。
1)、解决方法一打印运行时的堆栈信息来排查问题
为了找出到底是哪些对象没能被回收,我们加上运行参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.bin
,意思是发生OOM
时把堆内存信息dump
出来。运行程序直至异常,于是得到heap.dump
文件,然后我们借助eclipse
的MAT
插件来分析,然后File->Open Heap Dump
,然后选择刚才dump
出来的文件,选择Leak Suspects
,MAT
会列出所有可能发生内存泄漏的对象。
2)、解决方法二
在线上的应用,内存往往会设置得很大,这样发生OOM
再把内存快照dump
出来的文件就会很大,可能大到在本地的电脑中已经无法分析了(因为内存不足够打开这个dump
文件)。这里介绍另一种处理办法:
1)、用jps
定位到进程号。
2)、用jstat
分析gc
活动情况,jstat
是一个统计java
进程内存使用情况和gc
活动的工具,参数可以有很多,可以通过jstat -help
查看所有参数以及含义。
jstat -gcutil -t -h8 24836 1000
:
输出gc
的情况,输出时间,每8
行输出一个行头信息,统计的进程号是24836
,每1000
毫秒输出一次信息。
输出信息:Timestamp
是距离jvm
启动的时间,S0、S1、E
是新生代的两个Survivor
和Eden
,O
是老年代区,M
是Metaspace
,CCS
使用压缩比例,YGC
和YGCT
分别是新生代gc
的次数和时间,FGC
和FGC
T分别是老年代gc
的次数和时间,GCT
是gc
的总时间。虽然发生了gc
,但是老年代内存占用率根本没下降,说明有的对象没法被回收(当然也不排除这些对象真的是有用)。
3)、用jmap工具dump出内存快照
jmap
可以把指定java
进程的内存快照dump
出来,效果和第一种处理办法一样,不同的是它不用等OOM
就可以做到,而且dump
出来的快照也会小很多。
jmap -dump:live,format=b,file=heap.bin 24836
这时会得到heap.bin
的内存快照文件,然后就可以用eclipse
来分析了。
6、调优命令有哪些?
JDK
监控和故障处理命令有:jps
、jstat
、jmap
、jhat
、jstack
、jinfo
。
1、jps虚拟机进程状态工具
jps(JVM Process Status Tool)
,虚拟机进程状态工具,可以列出正在运行的虚拟机进程,并且显示虚拟机执行主类的名称,JVM启动参数以及这些进程的本地虚拟机的唯一ID(LVMID,Local Vitual Machine Identifier)
,它是使用频率最高的JDK
命令行工具,因为其他JDK
工具大多需要输入它查询到的LVMID
来确定要监控的是哪一个虚拟机进程。
对于本地虚拟机进程来说,LVMID
与操作系统进程ID(PID,Process Identifier)
是一致的。如果同时启动了多个虚拟机进程,无法根据进程名称定位时,那就只能依靠jps
命令显示主类的功能才能区分了。
命令格式:
jps [-q] [-mlvV] [<hostid>]
<hostid>: <hostname>[:<port>]
[option]
选项格式:lmqv
-
-q
:只输出LVMID
,省略主类的名称,66210
。 -
-m
:输出虚拟机进程启动时的参数,66210 jar --server.port=8888
。 -
-l
:输出主类的全名,如果进程执行的是jar
包,输出jar
路径,66210 Transaction-0.0.1-SNAPSHOT.jar
。 -
-v
:输出虚拟机进程启动时JVM
参数,66210 -Xmx1G -Xms1G
。 -
-V
:输出通过flag
文件传递到JVM
中的参数,66210 jar
。
2、jstat虚拟机统计信息监控工具
jstat(JVM Statistics Monitorning Tool)
,用于监控虚拟机各种运行状态信息的命令行工具。它可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,它是运行期定位虚拟机性能问题的首选工具。
jstat
命令中的参数 interval
和 count
代表查询间隔和次数,如果省略这两个参数,说明只查询一次。假设需要每250
毫秒查询一次进程2764
垃圾收集的情况,一共查询20
次,那么命令应该是:jstat -gc 2764 250 20
。
命令格式:
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
vmid
是jps
命令得到的
[option]
选项格式:
选项 | 作用 |
---|---|
-class | 监视类装载、卸载数量、总空间及类装载所耗费的时间 |
-compiler | 输出JIT编译器编译过的方法、耗时等信息 |
-gc | 监视Java堆状况,包括Eden区、2个Survivor区、老年代、永久代等容量、已用空间、GC合计时间等信息 |
-gccapacity | 监视内容与-gc基本相同,但输出主要关注java堆各区域使用到的最大和最小空间 |
-gcutil | 监控内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比 |
-gccause | 与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因 |
-gcnew | 监视新生代GC的状况 |
-gcnewcapacity | 监视内容与-gcnew基本相同,输出主要关注使用到的最大和最小空间 |
-gcold | 监视老年代GC的状况 |
-gcoldcapacity | 监视内容与-gcold基本相同,输出主要关注使用到的最大和最小空间 |
-gcpermcapacity(1.6)、-gcmetacapacity(1.8) | 输出永久代(元数据区)使用到的最大和最小空间 |
-printcompilation | 输出已被JIT编译的方法 |
具体的信息:
统计加载类的信息:jstat -class
编译统计:jstat -compiler
垃圾回收统计:jstat -gc
统计gc信息:jstat -gcutil
堆内存统计:jstat -gccapacity
最近二次gc统计:gstat -gccause
新生代垃圾回收统计:jstat -gcnew
新生代内存统计:jstat -gcnewcapacity
老年代垃圾回收统计:jstat -gcold
老年代内存统计:jstat -gcoldcapacity
永久代内存统计:jstat -gcmetacapacity|gcpermcapacity
JVM编译方法统计:jstat -printcompilation
3、jmap java内存映射工具
jmap(Memory Map for Java)
,用于生成heap dump
文件,可以获得运行中的jvm
的堆的快照,从而可以离线分析堆,以检查内存泄漏,检查一些严重影响性能的大对象的创建,检查系统中什么对象最多,各种对象所占内存的大小等等。jmap
的作用并不仅仅是为了获取dump
文件,它还可以查询finalize
执行队列,java
堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。
如果不想使用jmap
命令,要想获取Java
堆转储快照还有一些比较暴力的手段:
1)、-XX:+HeapDumpOnOutOfMemoryError
参数,可以让虚拟机在OOM
异常出现之后自动生成dump
文件。
2)、-XX:+HeapDumpOnCtrlBreak
参数,可以使用[ctrl]+[Break]
键让虚拟机生成dump
文件。
3)、在Linux
系统下通过kill -3
命令发送进程退出信息也能拿到dump
文件。
命令格式:
jmap [option] <pid>
jmap [option] <executable <core>
jmap [option] [server_id@]<remote server IP or hostname>
[option]
选项格式:
<none>
:打印与Solaris pmap
相同的信息。
-heap
:显示堆详细信息,如使用哪种回收期、参数配置、分带状况等,只在linux/solaris
平台下有效。
-histo[:live]
:显示堆中对象统计信息,包括类、实例数量和合计容量。如果指定了live
子选项,则仅计算live
对象。
-clstats
:打印类加载器统计信息。
-finalizerinfo
:打印等待最终确定的对象的信息;显示在F-Queue
中等待Finalizer
线程执行finalize
方法的对象,只在linux/solaris
平台下有效。
-F
:强制,当<pid>
没有响应时,使用-dump:<dump-options> <pid>
或者是-histo
强制生成一个heap dump
或者是histogram
,此模式不支持live
子选项。
-J<flag>
:将<flag>
直接传递给运行时系统
-dump:<dump-options>
:生成java
堆转储快照,格式为:
jmap -dump:live,format=b,file=heap.bin <pid>
。
[dump-options]
选项参数:
live
:仅转储活动对象,如果未指定,则转储堆中的所有对象
format=b
:二进制格式
file=<file>
:转储堆到file
【使用示例】:
执行jmap -dump
可以转储堆内存快照到指定文件:
格式:jmap -dump:[live,]format=b,file=文件路径 pid
jmap -dump:live,format=b,file=D:\zhangshixing\temp\heap.bin 4208
jmap -F -dump:live,format=b,file=D:\zhangshixing\temp\heap.bin 4208
可以把当前堆内存的快照转储到文件中,然后可以对内存快照进行分析。
显示堆详细信息:jmap -heap pid
打印类加载器统计信息:jmap -clstats pid
打印等待最终确定的对象的信息:jmap -finalizerinfo pid
显示堆中对象统计信息,包括类、实例数量和合计容量:jmap -histo[:live] pid
执行jmap -histo pid
可以打印出当前堆中所有每个类的实例数量和内存占用,class name
是每个类的类名([B是 byte类型,[C是char类型,[I是int 类型),bytes是这个类的所有示例占用内存大小,instances是这个类的实例数量。
4、jhat虚拟机堆转储快照分析工具
jhat
也是jdk
内置的工具之一,主要是用来分析jmap
生成的堆dump
,可以将堆中的对象以html
的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言。
命令格式:
jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>
参数格式:
-J<flag>
:将直接传递到运行时系统。例如,-J-mx512m
可以使用512MB
的最大堆大小。
-stack false
:关闭跟踪对象分配调用堆栈。
-refs false
:关闭对对象引用的跟踪。
-port <port>
:设置HTTP
服务器的端口,默认7000
。
-exclude <file>
:指定一个文件,该文件列出应从reachableFrom
查询中排除的数据成员。
-baseline <file>
:指定基准对象转储,两个堆转储中具有相同ID
和相同类的对象将被标记为非新对象。
-debug <int>
:设置调试级别:
0
:无调试输出1
:调试hprof文件解析2
:调试hprof文件解析,无服务器
-version
:报告版本号
<file>
:要读取的文件
【使用示例】:
导出程序执行的堆信息:
jmap -dump:format=b,file=/tmp/1.hprof 82686
使用jhat
分析堆文件:
jhat /tmp/1.hprof
访问http://localhost:7000/
查看html
分析内存泄露问题主要会用到Show heap histogram
和Execute Object Query Language (OQL) query
,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语言,使用类似于SQL
的语法对内存对象进行查询统计。
显示出堆中所包含的所有的类:All classes including platform
从根集能引用到的对象:Show all members of the rootset
显示所有类(包括平台)的实例计数:
Show instance counts for all classes (including platform)
Show instance counts for all classes (excluding platform)
堆实例的分布表:Show heap histogram
执行对象查询语句:Execute Object Query Language (OQL) query
显示finalizer
的统计信息:Show finalizer summary
5、jstack java堆栈跟踪工具
Jstack(stack trace for java)
:是java
虚拟机自带的一种堆栈跟踪工具,jstack
主要用于生成java
虚拟机当前时刻的线程快照,线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
线程出现停顿的时候通过jstack
来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
jstack
主要分为两个功能:
- 针对活着的进程做本地的或远程的线程
dump
- 针对
core
文件做线程dump
当指定的进程在64
位Java
虚拟机上运行时,可能需要指定-J-d64
选项,例如:jstack -J-d64 -m pid
。
jstack
命令格式:
jstack [-l] <pid>
jstack -F [-m] [-l] <pid>
jstack [-m] [-l] <executable> <core>
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
executable
:产生core dump
的Java
可执行程序。
core
:要打印的堆栈跟踪的核心文件。
server-id
:当多个DEBUG
服务器在同一远程主机上运行时,可使用的可选唯一ID
。
remote-hostname-or-IP
:远程DEBUG
的服务器主机名或IP
地址。
Options
:
-F
:当 jstack <pid>
没有响应时,强制打印一个堆栈转储。
-m
:打印包含Java
和本机C/ C++
帧的混合模式堆栈跟踪。
-l
:打印关于锁的附加信息,比如拥有的juc ownable
同步器的列表,会使得JVM
停顿得长久得多。
【使用示例】:
jstack pid
第一行各个单词的解析:
"Attach Listener" #17 daemon prio=9 os_prio=0 tid=0x00007f8a38002800 nid=0x15565 waiting on condition [0x0000000000000000]
daemon
:线程名称prio
:线程优先级tid
:指Java Thread id
nid
:指native
线程的id
[0x0000000000000000]
:线程栈起始地址
可以查看:死循环、Object.wait()
情况、死锁情况、等待IO
。
6、jinfo虚拟机配置信息工具
jinfo(Configuration Info for Java)
,作用是实时地查看和调整虚拟机的各项参数。
使用jps -v
可以查看虚拟机启动时显示指定的参数列表,但是如果想知道未被显示指定的参数的系统默认值,除了去找资料外,就只能使用jinfo
的-flag
选项进行查询了。
命令格式:
jinfo [option] <pid>
jinfo [option] <executable <core>
jinfo [option] [server_id@]<remote server IP or hostname>
option
选项的参数:
-flags
:打印VM
标志。
-flag <name>
:打印命名VM
标志的值。
-flag [+|-]<name>
:启用或禁用命名的VM
标志。
-flag <name>=<value>
:将命名的VM
标志设置为给定值。
-sysprops
:打印Java
系统属性。
<no option>
:打印以上两者。
【使用示例】:
打印所有的JVM
标志信息:jinfo -flags pid
打印指定的JVM
参数信息:jinfo -flag name pid
jinfo -flag MaxHeapSize 82686
启用或者禁用指定的JVM
参数:jinfo -flag [+|-] name pid
jinfo -flag +HeapDumpOnOutOfMemoryError 82686
jinfo -flag -HeapDumpOnOutOfMemoryError 82686
给指定的JVM
参数设置值:jinfo -flag name=value pid
jinfo -flag HeapDumpPath=/tmp/data 82686
打印系统参数信息,打印的信息和System.getProperties()
一样:jinfo -sysprops pid
打印以上所有配置信息:jinfo pid
7、调优工具?
常用调优工具分为两类:
JDK
自带监控工具:jconsole
和jvisualvm
。
第三方工具:MAT
、GChisto
、GCViewer
、JProfiler
、arthas
、async-profile
。
1、jconsole Java监视与管理控制台
jconsole(java monitoring and management console)
,是一款基于JMX (Java Management Extensions)
的可视化监视和管理工具,jconsole
用于对JVM
中内存,线程和类等的监控。
【使用jconsole】:
1、在linux
和windows
下通过jconsole
启动即可。
2、然后会自动搜索本机运行的所有虚拟机进程。
3、选择其中一个进程可开始进行监控。
运行程序,然后使用jconsole
进行监控,注意设置虚拟机参数。
配置启动参数:-Xms100M -XX:+UseSerialGC -XX:+PrintGCDetails
。
【jconsole基本介绍】:
jconsole
基本包括以下基本功能:概述、内存、线程、类、VM
概要、MBean
。
可以切换顶部的选项卡查看各种指标信息。
【内存监控】:
内存页签相当于可视化的jstat
命令,用于监视受收集器管理的虚拟机内存的变换趋势。
代码运行,控制台也会输出gc
日志。
【线程监控】:
线程页签的功能相当于可视化的jstack
命令,遇到线程停顿时可以使用这个页签进行监控分析。线程长时间停顿的主要原因主要有:等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待(活锁和死锁)。该窗口可以看到与代码中函数名字相关的函数,点击进入可以看到相关的状态和信息。通过线程这个窗口可以很方便查询虚拟机中的线程堆栈信息,对发现系统中的一些问题非常有帮助。可以在死锁的页面点击相关死锁的类来查看死锁信息。
关于程序死锁的,我们还可以使用命令行工具jstack
来查看java
线程堆栈信息,也可以发现死锁。
2、jvisualvm多合一故障处理工具
jvisualvm
是一款免费的,集成了多个JDK
命令行工具的可视化工具,它能为您提供强大的分析能力,对Java
应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和CPU
分析,同时它还支持在MBeans
上进行浏览和操作。jvisualvm
可以分析内存快照、线程快照、监控内存变化、GC
变化等。
可以在linux
和windows
下通过jvisualvm
启动。
【查看JVM配置信息】:
进入界面之后点击左边窗口显示正在运行的java
进程,右侧窗口包含概述、监视、线程、抽样器、Profiler
。点击右侧窗口概述,可以查看各种配置信息。通过jdk
提供的jinfo
命令工具也可以查看上面的信息。
可以查看cpu
、内存、类、线程监控信息。
【查看堆的变化】:
在jvisualvm
可以很清晰的看到堆内存变化信息。
【查看堆快照】:
点击监视->堆(dump)
可以生产堆快照信息,生成了以heapdump
开头的一个选项卡。对于堆dump
来说,在远程监控jvm
的时候,jvisualvm
是没有这个功能的,只有本地监控的时候才有。
【导出堆快照文件】:
查看堆快照,此步骤可以参考上面的查看堆快照功能。右键点击另存为,即可导出hprof
堆快照文件,可以发给其他同事分析使用。
查看class
对象加载信息:打开visualvm
查看,metaspace
。
【CPU分析,发现cpu使用率最高的方法】:
CPU
性能分析的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的CPU
使用情况,过高的CPU
使用率可能是我们的程序代码性能有问题导致的。可以切换到抽样器对cpu
进行采样,可以擦看到哪个方法占用的cpu
最高,然后进行优化。
【查看线程快照】:
发现死锁问题
如果查看的是远程服务器的JVM
,程序启动需要加上如下参数:
"-Dcom.sun.management.jmxremote=true"
"-Djava.rmi.server.hostname=12.34.56.78"
"-Dcom.sun.management.jmxremote.port=18181"
"-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false"
3、MAT
MAT
一个基于Eclipse
的内存分析工具,可以帮助我们查找内存泄漏和减少内存消耗。
4、GChisto
GChisto
一款专业分析GC
日志的工具。
8、让bug无处藏身,Java线上问题排查神器,你学会了吗?
本文总结了一些常见的线上应急现象和对应排查步骤和工具。
在线上应急过程中要记住,只有一个总体目标:尽快恢复服务,消除影响。
在大多数情况下,我们都是先优先恢复服务,保留下当时的异常信息(内存dump、线程dump、gc log等等,在紧急情况下甚至可以不用保留,等到事后去复现),等到服务正常,再去复盘问题。
【1、常见现象:CPU利用率高/飙升】
场景预设:监控系统突然告警,提示服务器负载异常。
预先说明:CPU飙升只是一种现象,其中具体的问题可能有很多种,这里只是借这个现象切入。
注:CPU使用率是衡量系统繁忙程度的重要指标,但是CPU使用率的安全阈值是相对的,取决于你的系统的IO密集型还是计算密集型。一般计算密集型应用CPU使用率偏高Load偏低,IO密集型CPU使用率偏低Load偏高。
CPU使用率:一段时间内CPU的使用状况,从这个指标可以看出某一段时间内CPU资源被占用的情况。
Load Average:一段时间内,CPU正在处理以及等待CPU处理的进程数的之和,Load Average是从另一个角度来体现CPU的使用状态的。
常见原因:
- 频繁gc
- 死循环、线程阻塞、io wait…
打包成jar之后,在服务器上运行,java -jar xx.jar &
【第一步:定位出问题的线程】
【方法a: 传统的方法】
1、top
定位CPU最高的进程
执行top命令,查看所有进程占系统CPU的排序,定位是哪个进程搞的鬼。
PID那一列就是进程号,例如是12816,%CPU是CPU的使用率。
2、top -Hp pid
定位使用CPU最高的线程
PID那一列就是线程号,假如是12817。
3、printf '0x%x' tid
线程id转化16进制
printf '0x%x' 12817 -> 0x3211
4、jstack pid | grep tid
找到线程堆栈
jstack 12816 | grep 0x3211 -A 30
【方法b:show-busy-java-threads】
这个脚本来自于github
上一个开源项目,项目提供了很多有用的脚本,show-busy-java-threads
就是其中的一个。使用这个脚本,可以直接简化方法A中的繁琐步骤。
curl -o show-busy-java-threads https://raw.github.com/oldratlee/useful-scripts/release-2.x/bin/show-busy-java-threads
chmod +x show-busy-java-threads
./show-busy-java-threads
show-busy-java-threads
:
- 从所有运行的
Java
进程中找出最消耗CPU
的线程(缺省5
个),打印出其线程栈。 - 缺省会自动从所有的
Java
进程中找出最消耗CPU
的线程,这样用更方便。 - 当然你可以手动指定要分析的
Java
进程Id
,以保证只会显示你关心的那个Java
进程的信息。
show-busy-java-threads -p <指定的Java进程Id>
show-busy-java-threads -c <要显示的线程栈数>
【方法 c: arthas thread】
阿里开源的arthas
现在已经几乎包揽了我们线上排查问题的工作,提供了一个很完整的工具集。在这个场景中,也只需要一个thread -n
命令即可。
# 下载
curl -o arthas-boot.jar https://arthas.gitee.io/arthas-boot.jar
java -jar arthas-boot.jar
输入数字选择程序,例如输入1
。
然后输入命令:thread -n 5
。
输入exit
退出。
要注意的是arthas
的cpu
占比,和前面两种cpu
占比统计方式不同。前面两种针对的是Java
进程启动开始到现在的cpu
占比情况,arthas
这种是一段采样间隔内,当前JVM
里各个线程所占用的cpu
时间占总cpu
时间的百分比。具体见官网:https://alibaba.github.io/arthas/thread.html
。
【第二步:分析问题】
通过第一步,找出有问题的代码之后,观察到线程栈之后,我们就要根据具体问题来具体分析。
【情况一:发现使用CPU最高的都是GC线程】
GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fd99001f800 nid=0x779 runnable
GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fd990021800 nid=0x77a runnable
GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007fd990023000 nid=0x77b runnable
GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007fd990025000 nid=0x77c runnable
gc
排查的内容较多,在后面单独讲述。
【情况二:发现使用CPU最高的是业务线程】
1、io wait
- 可能是因为磁盘空间不够导致的
IO
阻塞。
2、等待内核态锁,如Synchronized
jstack -l pid | grep BLOCKED
查看阻塞态线程堆栈。dump
线程栈,分析线程持锁情况。arthas
提供了thread -b
,可以找出当前阻塞其他线程的线程,针对Synchronized
情况。
【2、常见现象:频繁GC】
接前面的内容,这个情况下,我们自然而然想到去查看gc的具体情况。
- 方法a:查看
gc
日志。 - 方法b:
jstat -gcutil
进程号,统计间隔毫秒,统计次数(缺省代表一致统计)。 - 方法c:如果所在公司有对应用进行监控的组件当然更方便(比如
Prometheus + Grafana
)。
这里对开启gc log
进行补充说明,一个常常被讨论的问题是在生产环境中GC
日志是否应该开启,因为它所产生的开销通常都非常有限,因此我的答案是需要开启。但并不一定在启动JVM
时就必须指定GC
日志参数。HotSpot JVM
有一类特别的参数叫做可管理的参数,对于这些参数,可以在运行时修改他们的值。我们这里所讨论的所有参数以及以PrintGC
开头的参数都是可管理的参数,这样在任何时候我们都可以开启或是关闭GC
日志。比如我们可以使用JDK
自带的jinfo
工具来设置这些参数,或者是通过JMX
客户端调用HotSpotDiagnostic MXBean
的setVMOption
方法来设置这些参数。
这里再次大赞arthas
,它提供的vmoption
命令可以直接查看,更新VM
诊断相关的参数。
获取到gc
日志之后,可以上传到GC easy
帮助分析,得到可视化的图表分析结果。
【GC原因及定位】
prommotion failed
:从S
区晋升的对象在老年代也放不下导致FullGC
(FullGC
回收无效则抛OOM
)。
可能原因:
survivor
区太小,对象过早进入老年代:查看SurvivorRatio
参数。- 大对象分配,没有足够的内存:
dump
堆,profiler/MAT
分析对象占用情况。 old
区存在大量对象:dump
堆,profiler/MAT
分析对象占用情况。
你也可以从full GC
的效果来推断问题,正常情况下,一次full GC
应该会回收大量内存,所以正常的堆内存曲线应该是呈锯齿形。
如果你发现full gc
之后堆内存几乎没有下降,那么可以推断:堆中有大量不能回收的对象且在不停膨胀,使堆的使用占比超过full GC
的触发阈值,但又回收不掉,导致full GC
一直执行。换句话来说,可能是内存泄露了。
一般来说,GC
相关的异常推断都需要涉及到内存分析,使用jmap
之类的工具dump
出内存快照(或者Arthas
的heapdump
)命令,然后使用MAT
、JProfiler
、JVisualVM
等可视化内存分析工具。
至于内存分析之后的步骤,就需要小伙伴们根据具体问题具体分析啦。
【3、常见现象:线程池异常】
场景预设:业务监控突然告警,或者外部反馈提示大量请求执行失败。
异常说明:Java
线程池以有界队列的线程池为例,当新任务提交时,如果运行的线程少于 corePoolSize
,则创建新线程来处理请求。如果正在运行的线程数等于 corePoolSize
时,则新任务被添加到队列中,直到队列满。当队列满了后,会继续开辟新线程来处理任务,但不超过 maximumPoolSize
。当任务队列满了并且已开辟了最大线程数,此时又来了新任务,ThreadPoolExecutor
会拒绝服务。
常见问题和原因:
这种线程池异常,一般可以通过开发查看日志查出原因,有以下几种原因:
【1、下游服务,响应时间(RT)过长】
这种情况有可能是因为下游服务异常导致的,作为消费者我们要设置合适的超时时间和熔断降级机制。
另外针对这种情况,一般都要有对应的监控机制:比如日志监控、metrics
监控告警等,不要等到目标用户感觉到异常,从外部反映进来问题才去看日志查。
【2、数据库慢,sql或者数据库死锁】
查看日志中相关的关键词。
【3、Java 代码死锁】
jstack -l pid | grep -i -E 'BLOCKED | deadlock'
【4、常见问题恢复】
对于上文提到的一些问题,这里总结了一些恢复的方法。
【5、Arthas】
Arthas
是阿里巴巴开源的Java
诊断工具,基于Java Agent
方式,使用 Instrumentation
方式修改字节码方式进行Java
应用诊断。
1)、dashboard
:系统实时数据面板,可查看线程,内存,gc
等信息。
2)、thread
:查看当前线程信息,查看线程的堆栈,如查看最繁忙的前n
线程。
3)、getstatic
:获取静态属性值,如 getstatic className attrName
可用于查看线上开关真实值。
4)、sc
:查看jvm
已加载类信息,可用于排查jar
包冲突。
5)、sm
:查看jvm
已加载类的方法信息。
6)、jad
:反编译jvm
加载类信息,排查代码逻辑没执行原因。
7)、logger
:查看logger
信息,更新logger level
。
8)、watch
:观测方法执行数据,包含出参、入参、异常等。
9)、trace
:方法内部调用时长,并输出每个节点的耗时,用于性能分析。
10)、tt
:用于记录方法,并做回放。
另外,Arthas
里还集成了ognl
这个轻量级的表达式引擎,通过ognl
,你可以用arthas
实现很多的骚操作。
【6、涉及工具】
Arthas
(超级推荐)
useful-scripts
GC easy
Smart Java thread dump analyzer - thread dump analysis in seconds
PerfMa
:Java
虚拟机参数/线程dump
/内存dump
分析
Linux
命令
Java N
板斧
MAT
、JProfiler
等可视化内存分析工具