4.1 jps

jps(Java Virtual Machine Process Status Tool)是JDK 1.5提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。

jps存放在JAVA_HOME/bin/jps,使用时为了方便请将JAVA_HOME/bin/加入到Path.


$> jps

23991 Jps

23789 BossMain

23651 Resin


比较常用的参数:

-l 输出应用程序main class的完整package名 或者 应用程序的jar文件完整路径名


$> jps -l

28729 sun.tools.jps.Jps

23789 com.asiainfo.aimc.bossbi.BossMain

23651 com.caucho.server.resin.Resin


-v 输出传递给JVM的参数


$> jps -v

23789 BossMain

28802 Jps -Denv.class.path=/data/aoxj/bossbi/twsecurity/java/trustwork140.jar:/data/aoxj/bossbi/twsecurity/java/:/data/aoxj/bossbi/twsecurity/java/twcmcc.jar:/data/aoxj/jdk15/lib/rt.jar:/data/aoxj/jdk15/lib/tools.jar -Dapplication.home=/data/aoxj/jdk15 -Xms8m

23651 Resin -Xss1m -Dresin.home=/data/aoxj/resin -Dserver.root=/data/aoxj/resin -Djava.util.logging.manager=com.caucho.log.LogManagerImpl  -Djavax.management.builder.initial=com.caucho.jmx.MBeanServerBuilderImpl


-m 输出传递给main 方法的参数,在嵌入式jvm上可能是null


$> jps -m

28715 Jps -m

23789 BossMain

23651 Resin -socketwait 32768 -stdout /data/aoxj/resin/log/stdout.log -stderr /data/aoxj/resin/log/stderr.log


这些命令也可以联合使用,如​​jps -lmv


$> jps -lmv

25611 sun.tools.jps.Jps -lmv -Denv.class.path=.:/usr/local/jdk8/lib/rt.jar:/home/tianshouzhi/download/clojure-1.7.0/clojure-1.7.0.jar -Dapplication.home=/usr/local/jdk8 -Xms8m

13789 website-0.0.1-SNAPSHOT.jar -Xms128m -Xmx512m -Xmn128m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:+DisableExplicitGC -XX:-OmitStackTraceInFastThrow -Djava.ext.dirs=/usr/local/jdk8/jre/lib/ext


注:jps命令有个地方很不好,似乎只能显示当前用户的java进程,要显示其他用户的还是只能用unix/linux的ps命令。

更多用法:请参考sun官方文档 

http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jps.html


4.2 jinfo

jinfo用于观察、修改运行中的java程序的运行环境参数:参数包括Java System属性和JVM命令行参数。

jinfo语法

jinfo [option] <pid>

其中<option> 是以下之一: 

    -flag <name>         打印指定name的 JVM flag

    -flag [+|-]<name>    启用或者禁用指定name的 JVM flag

    -flag <name>=<value> 设置指定名称的name flag 为给定的值

    -flags               打印 JVM flags

    -sysprops           打印Java 系统属性

    <no option>          不指定任何option,则打印出所有的JVM flags和sysprops

    -h | -help           打印帮助信息

观察所有的java系统属性和非默认的JVM Flags


[root@www wangxiaoxiao]# jinfo 15525

Attaching to process ID 15525, please wait...

Debugger attached successfully.

Server compiler detected.

JVM version is 25.65-b01


Java System Properties:

jboss.i18n.generate-proxies = true

java.runtime.name = Java(TM) SE Runtime Environment

java.vm.version = 25.65-b01

sun.boot.library.path = /usr/local/jdk8/jre/lib/amd64

java.protocol.handler.pkgs = null|org.springframework.boot.loader

java.vendor.url = http://java.oracle.com/

java.vm.vendor = Oracle Corporation

path.separator = :

file.encoding.pkg = sun.io

java.vm.name = Java HotSpot(TM) 64-Bit Server VM

sun.os.patch.level = unknown

sun.java.launcher = SUN_STANDARD

user.country = US

user.dir = /data/website

java.vm.specification.name = Java Virtual Machine Specification

PID = 15525

java.runtime.version = 1.8.0_65-b17

java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment

os.arch = amd64

java.endorsed.dirs = /usr/local/jdk8/jre/lib/endorsed

org.jboss.logging.provider = slf4j

line.separator =

java.io.tmpdir = /tmp

java.vm.specification.vendor = Oracle Corporation

os.name = Linux

sun.jnu.encoding = UTF-8

java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib

spring.beaninfo.ignore = true

java.specification.name = Java Platform API Specification

java.class.version = 52.0

sun.management.compiler = HotSpot 64-Bit Tiered Compilers

os.version = 2.6.32-431.23.3.el6.x86_64

user.home = /root

user.timezone = Asia/Harbin

catalina.useNaming = false

java.awt.printerjob = sun.print.PSPrinterJob

file.encoding = UTF-8

java.specification.version = 1.8

catalina.home = /tmp/tomcat.4517928592901920395.8081

user.name = root

java.class.path = website-0.0.1-SNAPSHOT.jar

java.vm.specification.version = 1.8

sun.arch.data.model = 64

sun.java.command = website-0.0.1-SNAPSHOT.jar

java.home = /usr/local/jdk8/jre

user.language = en

java.specification.vendor = Oracle Corporation

awt.toolkit = sun.awt.X11.XToolkit

java.vm.info = mixed mode

java.version = 1.8.0_65

java.ext.dirs = /usr/local/jdk8/jre/lib/ext

sun.boot.class.path = /usr/local/jdk8/jre/lib/resources.jar:/usr/local/jdk8/jre/lib/rt.jar:/usr/local/jdk8/jre/lib/sunrsasign.jar:/usr/local/jdk8/jre/lib/jsse.jar:/usr/local/jdk8/jre/lib/jce.jar:/usr/local/jdk8/jre/lib/charsets.jar:/usr/local/jdk8/jre/lib/jfr.jar:/usr/local/jdk8/jre/classes

java.awt.headless = true

java.vendor = Oracle Corporation

catalina.base = /tmp/tomcat.4517928592901920395.8081

file.separator = /

LOG_EXCEPTION_CONVERSION_WORD = %wEx

java.vendor.url.bug = http://bugreport.sun.com/bugreport/

sun.io.unicode.encoding = UnicodeLittle

sun.cpu.endian = little

sun.cpu.isalist =


VM Flags:

Non-default VM flags: -XX:CICompilerCount=2 -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:+DisableExplicitGC -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=536870912 -XX:MaxNewSize=134217728 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=134152192 -XX:OldPLABSize=16 -XX:OldSize=65536 -XX:-OmitStackTraceInFastThrow -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

Command line:  -Xms128m -Xmx512m -Xmn128m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:+DisableExplicitGC -XX:-OmitStackTraceInFastThrow -Djava.ext.dirs=/usr/local/jdk8/jre/lib/ext


注意,这里的VM Flags只打印出非默认的参数,对于默认的参数如何查看,请参考:http://ifeve.com/useful-jvm-flags-part-3-printing-all-xx-flags-and-their-values/

修改运行中的JVM Flags

jinfo还能够修改一部分运行期间能够调整的虚拟机参数。例如系统启动时,并没有指定这样的参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/java/dump.hprof,这两个参数用于内存dump分析,后面讲解jmap的时候会介绍。

则可以通过jinfo命令进行修改:



jinfo -flag +HeapDumpOnOutOfMemoryError 15525

jinfo -flag HeapDumpPath=/java/dump.hprof 15525


注意:

1、如果运行过程中,通过jinfo修改了,则修改后的值只能通过jinfo看到,jps是看不到的,jps命令只能看到启动时的jvm参数。

2、很多运行参数是不能调整的,如果出现这种异常,说明不能调整:

  1. [root@www wangxiaoxiao]# jinfo -flag SurvivorRatio=7 15525

  2. Exception in thread "main" com.sun.tools.attach.AttachOperationFailedException: flag 'SurvivorRatio' cannot be changed

最后,对于运行时的修改,jinfo命令好像存在一个bug,如下图所示,我们明明设置了HeapDumpPath=/java/dump.hprof,但是这里显示的却是null:

JVM合理理解大总结(二)_耐心阅读_jar

但是直接指定查看HeapDumpPath这个flag时,返回的又是我们设置的值。

JVM合理理解大总结(二)_耐心阅读_java_02


4.3 jmap与jhat

jmap(Java Memory Map)命令可以获得运行中的jvm的堆的快照,从而可以离线分析堆,以检查内存泄漏,检查一些严重影响性能的大对象的创建,检查系统中什么对象最多,各种对象所占内存的大小。

命令语法:


jmap [option] <pid>

其中pid可以通过jps命令获取,option可选项如下: 

-heap:打印jvm 内存整体使用情况 

-histo[:live]:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。 

-dump:<dump-options>:dump java内存到二进制文件中。

   dump-options: 

    live       是否只dump当前存活的对象,如果不指定,将会dump所有的对象,包括待回收的对象。

    format=b    文件的格式

    file=<file>  指定文件的输出位置

-permstat:打印permanent generation heap情况 

上述命令中的-histo和-dumo都包含一个live选项,如果指定live,在统计前会进行full gc,因此不加live的堆大小要大于加live堆的大小。

命令使用


1、打印jvm 内存整体使用情况

jmap -heap pid  

可以观察到New Generation(Eden Space,From Space,To Space),tenured generation,Perm Generation的内存使用情况



[root@www wangxiaoxiao]# jmap -heap 12446

Attaching to process ID 12446, please wait...

Debugger attached successfully.

Server compiler detected.

JVM version is 25.65-b01


using parallel threads in the new generation.

using thread-local object allocation.

Concurrent Mark-Sweep GC


Heap Configuration:

   MinHeapFreeRatio         = 40

   MaxHeapFreeRatio         = 70

   MaxHeapSize              = 536870912 (512.0MB)

   NewSize                  = 134152192 (127.9375MB)

   MaxNewSize               = 134217728 (128.0MB)

   OldSize                  = 65536 (0.0625MB)

   NewRatio                 = 2

   SurvivorRatio            = 8

   MetaspaceSize            = 21807104 (20.796875MB)

   CompressedClassSpaceSize = 1073741824 (1024.0MB)

   MaxMetaspaceSize         = 17592186044415 MB

   G1HeapRegionSize         = 0 (0.0MB)


Heap Usage:

New Generation (Eden + 1 Survivor Space):

   capacity = 120782848 (115.1875MB)

   used     = 80598816 (76.86502075195312MB)

   free     = 40184032 (38.322479248046875MB)

   66.73034899789745% used

Eden Space:

   capacity = 107413504 (102.4375MB)

   used     = 75663840 (72.15866088867188MB)

   free     = 31749664 (30.278839111328125MB)

   70.44164577295606% used

From Space:

   capacity = 13369344 (12.75MB)

   used     = 4934976 (4.70635986328125MB)

   free     = 8434368 (8.04364013671875MB)

   36.91262637867647% used

To Space:

   capacity = 13369344 (12.75MB)

   used     = 0 (0.0MB)

   free     = 13369344 (12.75MB)

   0.0% used

concurrent mark-sweep generation:

   capacity = 198795264 (189.5859375MB)

   used     = 138417264 (132.00498962402344MB)

   free     = 60378000 (57.58094787597656MB)

   69.62804908672271% used


44470 interned Strings occupying 5924248 bytes.


2、查看类名,对象数量,对象占用大小

jmap -histo[:live] pid 

可以观察heap中所有对象的情况(heap中所有生存的对象的情况)。包括对象数量和所占空间大小。 


[root@www wangxiaoxiao]# jmap -histo 12446 | more

 num     #instances         #bytes  class name

----------------------------------------------

   1:        648278       81563800  [C

   2:        252059       30068112  [B

   3:        502067       12049608  java.lang.String

   4:         53315        8825728  [I

   5:        114705        8823824  [Ljava.lang.Object;

   6:        210107        6723424  java.util.HashMap$Node

   7:         60821        5352248  java.lang.reflect.Method

   8:         62414        5060664  [S

   9:         54688        4812544  java.util.jar.JarEntry

  10:        131308        4201856  org.springframework.boot.loader.util.AsciiBytes

  11:         28337        3420512  [Ljava.util.HashMap$Node;

  12:         59001        3304056  org.springframework.boot.loader.jar.JarEntryData

  13:         87337        2794784  java.util.concurrent.ConcurrentHashMap$Node

  14:         69859        2794360  java.lang.ref.SoftReference

  15:         36298        2613456  java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask

  16:         22683        2497536  java.lang.Class

  17:         60280        2411200  java.util.LinkedHashMap$Entry

  18:         27206        1741184  java.net.URL

  19:         56643        1359432  java.util.concurrent.ConcurrentSkipListMap$Node

  20:         22687        1270472  java.util.LinkedHashMap

  21:         23984        1151232  java.util.HashMap

  22:         15122        1088784  org.springframework.boot.loader.jar.JarURLConnection

  23:         26521        1060840  java.util.TreeMap$Entry

  24:         57292         916672  java.lang.Object

  25:         27960         894720  java.util.Hashtable$Entry

  26:         36242         869808  java.util.concurrent.Executors$RunnableAdapter

  27:           931         838160  [Ljava.util.concurrent.ConcurrentHashMap$Node;


输出说明:

instances列:表示当前类有多少个实例。

bytes列:说明当前类的实例总共占用了多少个字节

class name列:表示的就是当前类的名称,class name 解读:

  • B代表byte 

  • C代表char 

  • D代表double 

  • F代表float 

  • I代表int 

  • J代表long 

  • Z代表boolean 

  • 前边有[代表数组,[I 就相当于int[] 

  • 对象用[L+类名表示 

手工dump内存到本地文件

jmap -dump:file=heap.map  12446


[root@www wangxiaoxiao]# jmap -dump:file=heap.map  12446

Dumping heap to /home/wangxiaoxiao/heap.map ...

Heap dump file created


jhat(Java Heap Analyse Tool)

用途:是用来分析java堆的命令,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言

使用:


[root@www wangxiaoxiao]# jhat heap.map #heap.map是通过jmap -dump:file=heap.map pid命令导出

Reading from heap.map...

Dump file created Tue Feb 14 10:50:34 CST 2017

Snapshot read, resolving...

Resolving 2570075 objects...

Chasing references, expect 514 dots

...........................................................................................................................

Eliminating duplicate references

.............................................................................................................................

Snapshot resolved.

Started HTTP server on port 7000

Server is ready.


访问 http://localhost:7000,就可以查看详细的内存信息

关于jhat,不打算多说,因为一般实际开发中,很少使用jhat来直接对内存dump文件进行分析。下面介绍一个MAT,这个用的比较多。


MAT(Memory Analyzer Tool)

MAT是一个eclipse的插件,上手起来比较快。它能够快速的分析dump文件,可以直观的看到各个对象在内存占用的量大小,以及类实例的数量,对象之间的引用关系,找出对象的GC Roots相关的信息,此外还能生成内存泄露报表,疑似泄露大对象的报表等等。

安装MAT


可以选择eclipse插件的方式安装

http://download.eclipse.org/mat/1.3/update-site/

也可以选择单独MAT程序下载安装

http://www.eclipse.org/mat/downloads.php


内存溢出时,自动保存dump文件

前面是手工导出内存dump映射文件,如果应用已经在线上运行,为了能获取应用出现内存溢出时获得heap dump文件,以便在之后分析,可以在JVM启动时指定参数:-XX:+HeapDumpOnOutOfMemoryError,JVM会在遇到OutOfMemoryError时保存一个“堆储快照”,并将其保存在一个文件中。 文件路径通过-XX:HeapDumpPath指定。

案例

  1. public class OomDemo {

  2.     public static void main(String[] args) {

  3.         Map<String,Long> map=new HashMap<String, Long>();

  4.         for (long i = 0; i < Long.MAX_VALUE; i++) {

  5.             map.put(i+"",i);

  6. ​        }​

  7. ​    }​

  8. ​}​

设置虚拟机参数为:-Xmx40m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\Java\dump.hprof

执行程序,很快会抛出异常:


java.lang.OutOfMemoryError: GC overhead limit exceeded

Dumping heap to D:\Java\dump ...

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

Heap dump file created [55940678 bytes in 0.556 secs]

at java.lang.StringBuilder.toString(StringBuilder.java:405)

at oom.OomDemo.main(OomDemo.java:13)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:606)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)


生成了文件之后,就可以通过MAT打开来进行分析

File->Open Heap Dump->选择文件

弹出如下界面:

JVM合理理解大总结(二)_耐心阅读_jar_03

选中第一个,点击finish,出现以下界面:

JVM合理理解大总结(二)_耐心阅读_java_04

可以看到,提示在主线程(main)的一个本地变量中使用了98.77%的内存,而这个本地变量就是java.util.HashMap$Entry[]。


4.4 jstat

jstat是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。

jstat语法

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

jstat支持的option通过jstat -optioins进行查看


[root@www wangxiaoxiao]# jstat -options

-class                      统计class loader行为信息 ,例如当前总共加载了多少个类

-compile                     统计HotSpot Just-in-Time compiler的行为

-gc                        统计jdk gc时heap信息 

-gccapacity                   统计不同的generations相应的heap容量情况 

-gccause                     统计gc的情况,(同-gcutil)和引起gc的事件 

-gcnew                      统计gc时,新生代的情况 

-gcnewcapacity                 统计gc时,新生代heap容量 

-gcold                      统计gc时,老年区的情况 

-gcoldcapacity                 统计gc时,老年区heap容量 

-gcpermcapacity                统计gc时,permanent区heap容量 

-gcutil                      统计gc时,heap情况 

-printcompilation               hotspot编译方法统计 


其中:

    -gc综合了-gcnew、-gcold的输出;

    -gccapacity综合了-gcnewcapacity、-gcoldcapacity、-gcpermcapacity的输出

统计JVM gc状况

JVM合理理解大总结(二)_耐心阅读_java_05

参数说明:

S0C    当前survivor space 0 的总容量 (KB).

S1C    当前survivor space 1 的总容量 (KB).

S0U    当前survivor space 0 已使用的容量 (KB).

S1U    当前survivor space 1 已使用的容量 (KB).

EC      当前 eden space 总容量 (KB).

EU     当前eden space已经使用的容量 (KB).

OC    当前 old space 总容量 (KB).

OU    当前old space 已使用容量(KB).

PC     当前 permanent space 总容量(KB).

PU     当前 permanent space 已使用容量 (KB).

YGC    从应用启动时到现在,年轻代young generation 发生GC Events的总次数.

YGCT    从应用启动时到现在, 年轻代Young generation 垃圾回收的总耗时.

FGC      从应用启动时到现在, full GC事件总次数.

FGCT     从应用启动时到现在, Full sc总耗时.

GCT     从应用启动时到现在, 垃圾回收总时间. GCT=YGCT+FGCT


上图说明了,总应用启动到现在,YGC总共发生了136次,总耗时YGCT=6.891秒,FGC发生了20次,总耗时FGCT=1.384秒,FGC+YGC总耗时GCT=8.275秒。

可以发现五条记录YGC、YGCT、YGC、YGCT、GCT值都是相同的,说明在我们统计的这段时间(每1秒统计1次,共5次),所以这5秒内没有发生gc事件。

此外,这里显示的S0C、S1C、EC、OC、PC显示的值,与jmap中都有相对应的条目,为了进行说明,我们通过​​jmap​​命令与这里的参数进行对比:


jmap -heap 15525

...

Heap Usage:

New Generation (Eden + 1 Survivor Space):

   capacity = 120782848 (115.1875MB)

   used     = 54471592 (51.948158264160156MB)

   free     = 66311256 (63.239341735839844MB)

   45.09878091299851% used

Eden Space:

   capacity = 107413504 (102.4375MB)   #对应EC

   used     = 51631152 (49.23930358886719MB) #对应EU

   free     = 55782352 (53.19819641113281MB)

   48.06765451018151% used

From Space:

   capacity = 13369344 (12.75MB)  #对应S0C

   used     = 2840440 (2.7088546752929688MB) #对应S0U

   free     = 10528904 (10.041145324707031MB)

   21.24591902190564% used

To Space:

   capacity = 13369344 (12.75MB) #对应S1C

   used     = 0 (0.0MB) #对应S1U

   free     = 13369344 (12.75MB)

   0.0% used

concurrent mark-sweep generation: #老年代使用了cms收集器

   capacity = 194482176 (185.47265625MB) #对应OC

   used     = 123681224 (117.95160675048828MB) #对应OU

   free     = 70800952 (67.52104949951172MB)

   63.59514611765759% used


注意在jstat中显示的值以kb为单位,乘以1024之后,就是通过jmap -heap命令显示对应的字节数。


4.5 jstack

jstack命令主要用于调试java程序运行过程中的线程堆栈信息,可以用于检测死锁,进程耗用cpu过高报警问题的排查。

jstack语法格式:


[root@www wangxiaoxiao]# jstack

Usage:

    jstack [-l] <pid>

    jstack -F [-m] [-l] <pid>

Options:

    -F  强制dump线程堆栈信息. 用于进程hung住, jstack <pid>命令没有响应的情况

    -m  同时打印java和本地(native)线程栈信息,m是mixed mode的简写

    -l  打印锁的额外信息


jstack命令会打印出所有的线程,包括用户自己启动的线程和jvm后台线程,我们主要关注的是用户线程,如


[root@www wangxiaoxiao]# jstack 15525

2017-02-14 21:10:02

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.65-b01 mixed mode):


"elasticsearch[Native][merge][T#1]" #98 daemon prio=5 os_prio=0 tid=0x00007f031c009000 nid=0x4129 waiting on condition [0x00007f02f61ee000]

   java.lang.Thread.State: WAITING (parking)

    at sun.misc.Unsafe.park(Native Method)

    - parking to wait for  <0x00000000eea589f0> (a org.elasticsearch.common.util.concurrent.EsExecutors$ExecutorScalingQueue)

    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)

    at java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:737)

    at java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:647)

    at java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1269)

    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)

    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

    at java.lang.Thread.run(Thread.java:745)

....


线程dump信息说明:

elasticsearch[Native][merge][T#1] 是我们为线程起的名字

daemon ​表示线程是否是守护线程

prio ​表示我们为线程设置的优先级

os_prio ​表示的对应的操作系统线程的优先级,由于并不是所有的操作系统都支持线程优先级,所以可能会出现都置为0的情况

tid ​是java中为这个线程的id

nid ​是这个线程对应的操作系统本地线程id,每一个java线程都有一个对应的操作系统线程

wait on condition表示当前线程处于等待状态,但是并没列出具体原因

java.lang.Thread.State: WAITING (parking) 也是表示的处于等待状态,括号中的内容说明了导致等待的原因,例如这里的parking说明是因为调用了 LockSupport.park方法导致等待

java.lang.Thread.State说明:

一个Thread对象可以有多个状态,在​java.lang.Thread.State​中,总共定义六种状态:

1、NEW

线程刚刚被创建,也就是已经new过了,但是还没有调用start()方法,​jstack命令不会列出处于此状态的线程信息

2、RUNNABLE #java.lang.Thread.State: RUNNABLE

RUNNABLE这个名字很具有欺骗性,很容易让人误以为处于这个状态的线程正在运行。事实上,这个状态只是表示,线程是可运行的。我们已经无数次提到过,一个单核CPU在同一时刻,只能运行一个线程。

3、BLOCKED # java.lang.Thread.State: BLOCKED (on object monitor)

线程处于阻塞状态,正在等待一个monitor lock。通常情况下,是因为本线程与其他线程公用了一个锁。其他在线程正在使用这个锁进入某个synchronized同步方法块或者方法,而本线程进入这个同步代码块也需要这个锁,最终导致本线程处于阻塞状态。

4、WAITING

等待状态,调用以下方法可能会导致一个线程处于等待状态:

  • Object.wait​​ 不指定超时时间 # java.lang.Thread.State: WAITING (on object monitor)
  • Thread.join​​ with no timeout

  • LockSupport.park #java.lang.Thread.State: WAITING (parking)

例如:对于wait()方法,一个线程处于等待状态,通常是在等待其他线程完成某个操作。本线程调用某个对象的wait()方法,其他线程处于完成之后,调用同一个对象的notify或者notifyAll()方法。​Object.wait()方法只能够在同步代码块中调用。调用了wait()方法后,会释放锁。

5、TIMED_WAITING

线程等待指定的时间,对于以下方法的调用,可能会导致线程处于这个状态:

Thread.sleep #java.lang.Thread.State: TIMED_WAITING (sleeping)

Object.wait​​ 指定超时时间 #java.lang.Thread.State: TIMED_WAITING (on object monitor)

Thread.join​​ with timeout

LockSupport.parkNanos #java.lang.Thread.State: TIMED_WAITING (parking)

LockSupport.parkUntil #java.lang.Thread.State: TIMED_WAITING (parking)


6、TERMINATED

线程终止。

说明,对于​ java.lang.Thread.State: WAITING (on object monitor)​和java.lang.Thread.State: TIMED_WAITING (on object monitor),对于这两个状态,是因为调用了Object的wait方法(前者没有指定超时,后者指定了超时),由于wait方法肯定要在syncronized代码中编写,因此肯定是如类似以下代码导致:

  1. synchronized(obj) {

  2. ​               .........​

  3. ​               obj.wait();​

  4. ​               .........​

  5. ​        }​

因此通常的堆栈信息中,必然后一个lock标记,如下:


"CM1" #21 daemon prio=5 os_prio=0 tid=0x00007f02f0d6d800 nid=0x3d48 in Object.wait() [0x00007f02fefef000]

   java.lang.Thread.State: WAITING (on object monitor)

        at java.lang.Object.wait(Native Method)

        at java.lang.Object.wait(Object.java:502)

        at java.util.TimerThread.mainLoop(Timer.java:526)

        - locked <0x00000000eca75aa8> (a java.util.TaskQueue)

        at java.util.TimerThread.run(Timer.java:505)


关于死锁

在 JAVA 5中加强了对死锁的检测。线程 Dump中可以直接报告出 Java级别的死锁,如下所示:


Found one Java-level deadlock:

=============================

"Thread-1":

waiting to lock monitor 0x0003f334 (object 0x22c19f18, a java.lang.Object),

which is held by "Thread-0"

"Thread-0":

waiting to lock monitor 0x0003f314 (object 0x22c19f20, a java.lang.Object),

which is held by "Thread-1"


关于nid

每个线程都有一个tid 和nid,tid是java中这个线程的编号,而nid(native id)是对应操作系统线程id。有的时候,我们会收到报警,说服务器,某个进程占用CPU过高,肯定是因为某个java线程有耗CPU资源的方法。

可以通过命令​​cat /proc/<pid>/task​​查看一个进程下面有哪些线程

JVM合理理解大总结(二)_耐心阅读_sed_06

可以通过"​​top -Hp​​"命令查看这个进程内所有线程的cpu和内容使用情况使用情况:

JVM合理理解大总结(二)_耐心阅读_sed_07

上例中看到12446进程中,12467线程占用cpu最高,是0.3%。要想看这个线程对应java哪个线程,首先用以下命令将12467成16进制:

JVM合理理解大总结(二)_耐心阅读_jar_08

接着使用jstack命令,显示堆栈信息:

JVM合理理解大总结(二)_耐心阅读_sed_09


4.6 jconsole与jvisualvm

jvisualvm同jconsole都是一个基于图形化界面的、可以查看本地及远程的JAVA GUI监控工具,可以认为jvisualvm是jconsole的升级版,因此这里不再介绍jconsole,只介绍jvisualvm。jvisualvm是一个综合性的分析工具,可以认为其整合了jstack、jmap、jinfo等众多调试工具的功能,并以图形界面展示.

jvisualvm启动很简单,直接在命令行中输入"jvisualvm"即可。之后出现下图:

JVM合理理解大总结(二)_耐心阅读_jar_10

侧边框介绍:

本地:如果你本地有java进程启动了,那么在本地这个栏目就会显示。

远程:就是监控的远程主机

由于本地和远程展示的监控界面都是相同的,这里直接介绍远程,远程监控回了,本地监控自然而然也会了。


注意,一个主机如果希望支持远程监控,需要在启动时添加以下参数:


-Dcom.sun.management.jmxremote.port=1099

-Dcom.sun.management.jmxremote.authenticate=false

-Dcom.sun.management.jmxremote.ssl=false


此外,-Dcom.sun.management.jmxremote  JDK5时,需要指定这个参数,开启JMX管理功能,JDK6之后,JMX管理默认开启,不需要指定


之后,右击"远程"-->"添加远程主机",出现界面

JVM合理理解大总结(二)_耐心阅读_java_11

在连接后面添加一个1099,这是远程主机jmx监听的端口号,点击确定,侧边栏变为:

JVM合理理解大总结(二)_耐心阅读_sed_12

点击红色框中的jmx连接,出现以下界面

JVM合理理解大总结(二)_耐心阅读_java_13

jvisualvm分为四个选项卡:概述、监视、线程、抽样器,下面我们一一介绍:

“概述 ”选项卡:

   默认显示的就是概述选项卡,其中的信息相当于我们调用了jinfo命令获得,其还包含了两个子选项卡:

     jvm参数栏:相当于我们调用jinfo -flags <pid>获得

     系统属性栏:相当于我们调用jinfo -sysprops <pid>获得

“监视”选项卡:

    主要显示了cpu、内存使用、类加载信息、线程信息等,这只是一个概要性的介绍,如下图:

JVM合理理解大总结(二)_耐心阅读_sed_14

右上角的​"堆dump"​会在远程主机上,dump一个内存映射文件,之所以不直接dump到本地,主要是因为这个文件通常比较大,直接dump到本地会很慢。

JVM合理理解大总结(二)_耐心阅读_java_15

dump完成之后,可以手工下载这个文件,通过​"文件"->"装入"​来进行分析,不过我们一般还是使用mat来进行分析,不会使用这个功能。

“线程”选项卡:

JVM合理理解大总结(二)_耐心阅读_java_16

线程选项卡列出了所有线程的信息,并使用了不同的颜色标记,右下角的颜色表示了不同的状态。

右上角的线程dump会直接把线程信息dump到本地,相当于调用了jstack命令,如:

JVM合理理解大总结(二)_耐心阅读_jar_17

“抽样器 ”选项卡:

JVM合理理解大总结(二)_耐心阅读_sed_18

主要有"cpu"和"内存"两个按钮,读者可以分别点击一下,看一下显示效果。


JVM合理理解大总结(二)_耐心阅读_jar_19

JVM合理理解大总结(二)_耐心阅读_sed_20JVM合理理解大总结(二)_耐心阅读_java_21JVM合理理解大总结(二)_耐心阅读_jar_22JVM合理理解大总结(二)_耐心阅读_jar_23