JVM调优

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)、垃圾回收日志分析工具:

使用工具如VisualVMJConsoleMAT (Memory Analyzer Tool)等来分析垃圾回收日志,找出内存泄漏、频繁GC等问题。

9)、JVM性能分析工具:

使用工具如JProfilerYourKit等来进行JVM性能分析,找出性能瓶颈并进行调优。

10)、线程参数:

-XX:ThreadStackSize设定线程栈大小等,确保线程的稳定运行。

不显式设置-Xss-XX:ThreadStackSize时,在Linux x64ThreadStackSize的默认值就是1024KB,给Java线程创建栈会用这个参数指定的大小。如果把-Xss或者-XX:ThreadStackSize设为0,就是使用系统默认值。而在Linux x64HotSpot 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.1top -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应用肯定是多线程的程序,它服务于多个请求,程序发生死锁后,死锁的线程处于等待状态(WAITINGTIMED_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文件,然后我们借助eclipseMAT插件来分析,然后File->Open Heap Dump,然后选择刚才dump出来的文件,选择Leak SuspectsMAT会列出所有可能发生内存泄漏的对象。

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是新生代的两个SurvivorEdenO是老年代区,MMetaspaceCCS使用压缩比例,YGCYGCT分别是新生代gc的次数和时间,FGCFGCT分别是老年代gc的次数和时间,GCTgc的总时间。虽然发生了gc,但是老年代内存占用率根本没下降,说明有的对象没法被回收(当然也不排除这些对象真的是有用)。

3)、用jmap工具dump出内存快照

jmap可以把指定java进程的内存快照dump出来,效果和第一种处理办法一样,不同的是它不用等OOM就可以做到,而且dump出来的快照也会小很多。

jmap -dump:live,format=b,file=heap.bin 24836

这时会得到heap.bin的内存快照文件,然后就可以用eclipse来分析了。

6、调优命令有哪些?

JDK监控和故障处理命令有:jpsjstatjmapjhatjstackjinfo

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命令中的参数 intervalcount代表查询间隔和次数,如果省略这两个参数,说明只查询一次。假设需要每250毫秒查询一次进程2764垃圾收集的情况,一共查询20次,那么命令应该是:jstat -gc 2764 250 20

命令格式:

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

vmidjps命令得到的

[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 histogramExecute 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

当指定的进程在64Java虚拟机上运行时,可能需要指定-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 dumpJava可执行程序。

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自带监控工具:jconsolejvisualvm

第三方工具:MATGChistoGCViewerJProfilerarthasasync-profile

1、jconsole Java监视与管理控制台

jconsole(java monitoring and management console),是一款基于JMX (Java Management Extensions)的可视化监视和管理工具,jconsole用于对JVM中内存,线程和类等的监控

【使用jconsole】:

1、在linuxwindows下通过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变化等。

可以在linuxwindows下通过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,程序启动需要加上如下参数:

properties 复制代码
"-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中的繁琐步骤。

shell 复制代码
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命令即可。

shell 复制代码
# 下载
curl -o arthas-boot.jar https://arthas.gitee.io/arthas-boot.jar
java -jar arthas-boot.jar

输入数字选择程序,例如输入1

然后输入命令:thread -n 5

输入exit退出。

要注意的是arthascpu占比,和前面两种cpu占比统计方式不同。前面两种针对的是Java进程启动开始到现在的cpu占比情况,arthas这种是一段采样间隔内,当前JVM里各个线程所占用的cpu时间占总cpu时间的百分比。具体见官网:https://alibaba.github.io/arthas/thread.html

【第二步:分析问题】

通过第一步,找出有问题的代码之后,观察到线程栈之后,我们就要根据具体问题来具体分析。

【情况一:发现使用CPU最高的都是GC线程】

shell 复制代码
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 MXBeansetVMOption方法来设置这些参数。

这里再次大赞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出内存快照(或者Arthasheapdump)命令,然后使用MATJProfilerJVisualVM等可视化内存分析工具。

至于内存分析之后的步骤,就需要小伙伴们根据具体问题具体分析啦。

【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

PerfMaJava虚拟机参数/线程dump/内存dump分析

Linux 命令

Java N板斧

MATJProfiler 等可视化内存分析工具

相关推荐
sky丶Mamba5 小时前
Java虚拟机启动时默认携带参数(jdk8)
java·jvm
王佑辉21 小时前
【jvm】为什么要有GC
jvm
极客先躯21 小时前
高级java每日一道面试题-2024年12月07日-JVM篇-如何选择垃圾收集器?
java·jvm·zgc·g1 gc·serial gc·parallel gc·cms gc
极客先躯21 小时前
高级java每日一道面试题-2024年12月06日-JVM篇-对象头具体都包含哪些内容?
java·jvm·mark word·hash code·锁信息·分代年龄·gc标志位
多敲代码防脱发1 天前
网络编程(2)(对于UDP与TCP协议深层理解)
java·开发语言·网络·jvm·tcp/ip·udp·java-ee
运筹帷幄小红花1 天前
GC常见垃圾回收算法,JVM分代模型
java·jvm·算法
墨️穹1 天前
数据结构DAY1
jvm·算法
zhlh_xt1 天前
Java应用服务器JVM配置与优化全面指南
java·jvm·java应用优化·jvm基础知识·jvm参数优化建议
·Lntano·远方2 天前
java垃圾回收机制中的引用计数算法
java·jvm·算法