
jstack在命令使用上十分简洁,然而其输出的内容却十分丰富,信息量足,值得深入分析;
以往对于jstack产生的threaddump,我很少字斟句酌得分析过每一部分细节,针对jstack的性能诊断也没有一个模式化的总结;今天这篇文章我就来详细整理一下与jstack相关的内容。
jstack命令的基本使用
jstack在命令使用上十分简洁,其信息量与复杂度主要体现在threaddump内容的分析上;
从coredump中提取threaddumpsudo-uxxxjstackcore_file_path可能会消耗较长时间sudo-uxxxjstack-l{vmid}jstack输出内容结构分析
首先展示几段threaddump的典型例子:
正在RUNNING中的线程:
"elasticsearch[datanode-39][[xxx_index_v4][9]:LuceneMergeThread45061daemonprio=5os_prio=0tid=0x00007fb968213800nid=0x249carunnable[0x00007fb6843c2000]:(:94)$(:626)
阻塞在上:
":20779-thread-510"26prio=5os_prio=0tid=0x00007f1830d3a800nid=0xdf64waitingformonitorentry[0x00007f16b5ef9000]:BLOCKED(onobjectmonitor)(:234)-waitingtolock0x00000000c07549f8()(:377)(:745)
"JFRrequesttimer"线程性能诊断的辅助脚本;//|-----线程名------||-线程创建次序-||是否守护进程||---线程优先级---||-------线程id-------||-所映射的linux轻量级进程id-||-------------线程动作--------------|"JFRrequesttimer"动作修饰,这些为线程死锁问题的排查提供了依据;(NativeMethod)-waitingon0x00007fba6b50ea38()(:502)(:526)-locked0x00007fba6b50ea38()(:505)线程的动作
线程动作的记录在每个threaddump的第一行末尾,一般情况下可分为如下几类:
runnable,表示线程在参与cpu资源的竞争,可能在被调度运行也可能在就绪等待;
sleeping,表示调用了(),线程进入休眠;
waitingformonitorentry[0x],表示线程在试图获取内置锁,进入了等待区EntrySet,方括号内的地址表示线程等待的资源地址;
()[0x],表示线程调用了(),放弃了内置锁,进入了等待区WaitSet,等待被唤醒,方括号内的地址表示线程放弃的资源地址;
waitingoncondition[0x],表示线程被阻塞原语所阻塞,方括号内的地址表示线程等待的资源地址;这种和jvm的内置锁体系没有关系,它是jdk5之后的包下的锁机制;
线程的状态
线程的状态记录在每个threaddump的第二行,并以开头,一般情况下可分为如下几类:
RUNNABLE,这种一般与线程动作runnable一起出现;
BLOCKED(onobjectmonitor),这种一般与线程动作waitingformonitorentry一起出现,不过在其线程调用栈最末端并没有一个固定的方法,因为synchronized关键字可以修饰各种方法或者同步块;
WAITING(onobjectmonitor)或者TIMED_WAITING(onobjectmonitor),这种一般与线程动作()[0x]一起出现,并且线程调用栈的最末端调用方法为(NativeMethod),以表示()方法的调用;
另外,WAITING与TIMED_WAITING的区别在于是否设置了超时中断,即wait(longtimeout)与wait()的区别;WAITING(parking)或者TIMED_WAITING(parking),这种一般与线程动作waitingoncondition[0x]一起出现,并且线程调用栈的最末端调用方法一般为(NativeMethod);
使用的是线程阻塞原语,主要在类中被使用到,很多基于AQS构建的同步工具,如ReentrantLock,Condition,CountDownLatch,Semaphore等都会诱发线程进入该状态;
另外,WAITING与TIMED_WAITING的区别与第三点中提到的原因一致;线程的重要调用修饰
threaddump的第三部分线程调用栈中,一般会把与锁相关的资源使用状态以附加的形式作重点修饰,这与线程的动作及状态有着密切的联系,一般情况下可分为如下几类:
locked0x,表示其成功获取了内置锁,成为了owner;
parkingtowaitfor0x,表示其被阻塞原语所阻塞,通常与线程动作waitingoncondition一起出现;
waitingtolock0x,表示其在EntrySet中等待某个内置锁,通常与线程动作waitingformonitorentry一起出现;
waitingon0x,表示其在WaitSet中等待被唤醒,通常与线程动作()[0x]一起出现;
另外,waitingon调用修饰往往与locked调用修饰一同出现,如之前列举的第四个threaddump:(NativeMethod)-waitingon0x00007fba6b50ea38()(:502)(:526)-locked0x00007fba6b50ea38()(:505)这是因为该线程之前获得过该内置锁,现在因为()又将其放弃了,所以在调用栈中会出现先后两个调用修饰;
死锁检测的展示
在jdk5之前,DougLea大神还没有发布包,这个时候提及的锁,就仅限于jvm监视器内置锁;此时如果进程内有死锁发生,jstack将会把死锁检测信息打印出来:
FoundoneJava-leveldeadlock:============================="Thread-xxx":waitingtolockmonitor0x00007f0134003ae8(object0x00000007d6aa2c98,),whichisheldby"Thread-yyy""Thread-yyy":waitingtolockmonitor0x00007f0134006168(object0x00000007d6aa2ca8,),whichisheldby"Thread-xxx"Javastackinformationforthethreadslistedabove:==================================================="Thread-xxx":"Thread-yyy":Found1deadlock.然而后来DougLea发布了包,当谈及java的锁,除了内置锁之外还有了基于AbstractOwnableSynchronizer的各种形式;由于是新事物,彼时jdk5的jstack没有及时提供对以AQS构建的同步工具的死锁检测功能,直到jdk6才完善了相关支持;
常见java进程的jstackdump特征首先,不管是什么类型的java应用,有一些通常都会存在的线程:
VMThread与VMPeriodicTaskThread虚拟机线程,属于nativethread,凌驾于其他用户线程之上;
VMPeriodicTaskThread通常用于虚拟机作sampling/profiling,收集系统运行信息,为JIT优化作决策依据;C1/C2CompilerThread虚拟机的JIT及时编译器线程:
"C1CompilerThread2"9daemonprio=9os_prio=0tid=0x00007feb34112000nid=0x18b1waitingoncondition[0x0000000000000000]:RUNNABLE"C2CompilerThread0"2daemonprio=10os_prio=0tid=0x00007f91e007f000nid=0()[0x]:WAITING(onobjectmonitor)(NativeMethod)(:502)$(:157)-locked0x00000000c0495140($Lock)Finalizer线程用于从referencequeue中取出对象以执行其finalize方法:
"Finalizer"ParallelScavenge"GCtaskthread1(ParallelGC)"os_prio=0tid=0x00007f91e0023000nid=0xa7brunnable0(ParallelGCThreads)"os_prio=0tid=0x00007feb3401e800nid=0x18a4runnable"GangworkerCMS"ConcurrentMark-SweepGCThread"os_prio=0tid=0x00007feb34066800nid=0x18a8runnable0(G1ParallelMarkingThreads)"os_prio=0tid=0x00007fc2f4093800nid=0x1805frunnable"Gangworker0"os_prio=0tid=0x00007fc2f4079000nid=0x1805drunnable"G1ConcurrentRefinementThread-ppid:只显示指定进程的信息使用P快捷键按cpu使用率排序,并记录排序靠前的若干pid(轻量级进程id)作进制转换:
!/bin/shdefault_lines=10top_head_info_padding_lines=8default_stack_lines=15jvm_pid=$1jvm_user=$2((thread_stack_lines=${3:-$default_lines}+top_head_info_padding_lines))threads_top_capture=$(top-b-n1-H-p$jvm_pid|grep$jvm_user|head-n$thread_stack_lines)jstack_output=$(echo"$(sudo-i-u$jvm_userjstack$jvm_pid)")top_output=$(echo"$(echo"$threads_top_capture"|perl-pe's/\e\[?.*?[\@-~]?//g'|awk'{gsub(/^+/,"");print}'|awk'{gsub(/+|[+-]/,"");print}'|cut-d""-f1,9)\n")echo"***********************************************************"uptimeecho"Analyzingtop$top_threadsthreads"echo"***********************************************************"printf%s"$top_output"|whileIFS=readlinedopid=$(echo$line|cut-d""-f1)hexapid=$(printf"%x"$pid)cpu=$(echo$line|cut-d""-f2)echo-n$cpu"%[$pid]"echo"$jstack_output"|grep"tid.*0x$hexapid"-A$default_stack_lines|sed-n-e'/0x'$hexapid'/,/tid/p'|head-n-1done该脚本有多种版本,在我司的每台主机上的指定路径下都存放了一个副本;出于保密协议,该脚本源码不便于公开,上方所展示的版本是基于美团点评的技术专家王锐老师在一次问答分享中给出的代码所改造的;
threaddump可视化分析工具与一道,同出自一家之手:。