java應用監測(3)-這些命令行工具你掌握了嗎

tags: java, troubleshooting, monitor,jvmhtml


一句話歸納:原來jdk自帶的命令行工具如此好用,本文將詳細介紹。java

1 引言

監測java應用,最方便的就是直接使用jdk提供的現成工具,在jdk的安裝的bin目錄下,已經提供了多種命令行監測工具,以便於開發人員和運維人員監測java應用和診斷問題,所以,此類工具是java應用監測的重要手段。也是做爲java開發人員須要掌握的基本技能。linux

2 經常使用監測命令行工具

通常來講,經常使用的命令行工具包括jps,jinfo,jmap,jstack,jstat,這些工具都在JAVA_HOME/bin/目錄下,概要說明以下:git

  • jps查看java進程ID
  • jinfo查看及調整虛擬機參數
  • jmap查看堆(heap)使用狀況及生成堆快照
  • jstack查看線程運行狀態及生成線程快照
  • jstat顯示進程中的類裝載、內存、垃圾收集等運行數據。

經過這些工具,基本上能夠了解java應用的內存變化狀態,線程運行狀態等信息,進而爲應用監測及問題診斷提供依據。下面將結合實例對這些工具的使用進行詳細講解,文中所使用的示例代碼java-monitor-example已上傳到個人github,地址:https://github.com/mianshenglee程序員

3 進程查詢工具jps

3.1 jps說明

要監測java應用,第一步就是先知道這個應用是哪一個進程,它的運行參數是什麼。jps就是能夠查詢進程的工具。熟悉linux的同窗,大概都知道查詢進程使用ps -ef|grep java這樣的命令,jps也相似,但它不使用名稱查找,而是查找所有當前jdk運行的java進程,並且只查找當前用戶的Java進程,而不是當前系統中的全部進程。github

3.2 jps使用

做爲命令行工具,能夠經過-help參數查看幫助,也可查閱官方文檔https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jps.html,以下:shell

[root@test bin]# jps -help
usage: jps [-help]
       jps [-q] [-mlvV] [<hostid>]

Definitions:
    <hostid>:      <hostname>[:<port>]

參數解釋:
-q:只顯示java進程的pid
-m:輸出傳遞給main方法的參數,在嵌入式jvm上多是null
-l:輸出應用程序main class的完整package名 或者 應用程序的jar文件完整路徑名
-v:輸出傳遞給JVM的參數
複製代碼

示例工程java-monitor-example在linux機器中運行起來,使用jps可輸出如下信息:apache

  • 只輸出進程ID
[root@test bin]# jps -q
13680
14214
複製代碼
  • 輸出程序完整名稱及JVM參數
[root@test bin]# jps -lv
13680 java-monitor-example-0.0.1-SNAPSHOT.jar -Xms128m -Xmx128m -Dserver.port=8083
14289 sun.tools.jps.Jps -Denv.class.path=.:/opt/jdk8/lib:/opt/jdk8/jre/lib -Dapplication.home=/opt/jdk8
複製代碼

輸出的內容中,java-monitor-example-0.0.1-SNAPSHOT.jar-l輸出的完整名稱,-Xms128m -Xmx128m -Dserver.port=8083是傳給JVM的參數。api

  • 在shell腳本中使用命令獲取java進程ID並做爲變量使用
JAVA_HOME="/opt/jdk8"
APP_MAINCLASS=java-monitor-example
#初始化psid變量(全局)
psid=0
 #查看進程ID函數
checkpid() {
   javaps=`$JAVA_HOME/bin/jps -l | grep $APP_MAINCLASS`
 
   if [ -n "$javaps" ]; then
      psid=`echo $javaps | awk '{print $1}'`
   else
      psid=0
   fi
}
 #調用函數後經過psid進行業務邏輯操做,如根據進程id殺進程
checkpid
echo "(pid=$psid)"
複製代碼

上述腳本,比較適合運維人員對應用的開啓和關閉,自動獲取java進程ID,而後根據ID判斷程序是否運行(start),或者關閉應用(kill -9)。數組

4 配置信息工具jinfo

4.1 jinfo說明

知道java應用所屬的進程號是第一步,在上一篇文章《java應用監測(2)-java命令的祕密》中,已經知道java的啓動參數有不少,監測java應用前須要瞭解清楚它的啓動參數是什麼。這時就須要用到jinfo工具。jinfo能夠輸出JAVA應用的系統參數和JVM參數。jinfo還可以修改一部分運行期間可以調整的虛擬機參數,不少運行參數是不能調整的,若是出現"cannot be changed"異常,說明不能調整。不過官方文檔指出,這個命令在後續的版本中可能再也不使用,當前JDK8仍是能夠用的。

4.2 jinfo使用

經過-help參數查看幫助,也可查閱jinfo官方文檔說明https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jinfo.html,以下:

[root@test bin]# jinfo -help
Usage:
    jinfo [option] <pid>
        (to connect to running process)

where <option> is one of:
    -flag <name>         to print the value of the named VM flag
    -flag [+|-]<name>    to enable or disable the named VM flag
    -flag <name>=<value> to set the named VM flag to the given value
    -flags               to print VM flags
    -sysprops            to print Java system properties
    <no option>          to print both of the above
    -h | -help           to print this help message

複製代碼

使用jps獲取到應用的進程ID後(示例的PID爲13680),若是直接jps <pid>則會輸出所有的系統參數和JVM參數,其它參數說明在help中也說得很清楚了。下面仍是結合示例代碼java-monitor-example來實踐一下:

  • 獲取java應用的堆初始值
[root@test bin]# jinfo -flag InitialHeapSize 13680
-XX:InitialHeapSize=134217728
複製代碼
  • 查看所有的JVM參數
[root@test bin]# jinfo -flags 13680
Attaching to process ID 13680, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.51-b03
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=134217728 -XX:MaxNewSize=44564480 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=44564480 -XX:OldSize=89653248 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line:  -Xms128m -Xmx128m -Dserver.port=8083

複製代碼

可見,因爲咱們啓動時設置了-Xms-Xmx,它們對應的就是-XX:InitialHeapSize-XX:MaxHeapSize值。另外,參數-Dserver.port屬於系統參數,使用jinfo -sysprops 13680就能夠查看系統參數了。

5 堆內存查看工具jmap

5.1 jmap說明

java應用啓動後,它在JVM中運行,內存是須要重點監測的地方,jmap就是這樣的一個工具,它能夠獲取運行中的jvm的堆的快照,包括總體狀況,堆佔用狀況的直方圖,dump出快照文件以便於離線分析等。官方文檔指出,這個命令在後續的版本中可能再也不使用,當前JDK8仍是能夠用的。

5.2 jmap使用

經過-help參數查看幫助,也可查閱jmap官方文檔說明https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jmap.html,幫助說明以下:

[root@test bin]# jmap -help
Usage:
    jmap [option] <pid>
        (to connect to running process)

where <option> is one of:
    <none>               to print same info as Solaris pmap
    -heap                to print java heap summary
    -histo[:live]        to print histogram of java object heap; if the "live"
                         suboption is specified, only count live objects
    -clstats             to print class loader statistics
    -finalizerinfo       to print information on objects awaiting finalization
    -dump:<dump-options> to dump java heap in hprof binary format
                         dump-options:
                           live         dump only live objects; if not specified,
                                        all objects in the heap are dumped.
                           format=b     binary format
                           file=<file>  dump heap to <file>
                         Example: jmap -dump:live,format=b,file=heap.bin <pid>
    -F                   force. Use with -dump:<dump-options> <pid> or -histo
                         to force a heap dump or histogram when <pid> does not
                         respond. The "live" suboption is not supported
                         in this mode.
    -h | -help           to print this help message
    -J<flag>             to pass <flag> directly to the runtime system

複製代碼

如上所示,jmap參數經常使用的是-heap,-histo-dump,結合示例java-monitor-example,說明以下:

  • 打印jvm內存總體使用狀況
[root@test bin]# jmap -heap 13680
Attaching to process ID 13680, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.51-b03

using thread-local object allocation.
Parallel GC with 2 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 134217728 (128.0MB)
   NewSize                  = 44564480 (42.5MB)
   MaxNewSize               = 44564480 (42.5MB)
   OldSize                  = 89653248 (85.5MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 31981568 (30.5MB)
   used     = 5306632 (5.060798645019531MB)
   free     = 26674936 (25.43920135498047MB)
   16.59278244268699% used
From Space:
   capacity = 6291456 (6.0MB)
   used     = 1081440 (1.031341552734375MB)
   free     = 5210016 (4.968658447265625MB)
   17.18902587890625% used
To Space:
   capacity = 6291456 (6.0MB)
   used     = 0 (0.0MB)
   free     = 6291456 (6.0MB)
   0.0% used
PS Old Generation
   capacity = 89653248 (85.5MB)
   used     = 16615680 (15.845947265625MB)
   free     = 73037568 (69.654052734375MB)
   18.533271655701753% used

18006 interned Strings occupying 2328928 bytes.

複製代碼

從以上信息,能夠看出JVM中堆內存當前的使用狀況,包括年輕代(Eden區,From區,To區)和年老代。

  • 查看類名,對象數量,對象佔用大小直方圖
[root@test bin]# jmap -histo:live 13680|more

 num     #instances         #bytes  class name
----------------------------------------------
   1:         36536        6462912  [C
   2:         35557         853368  java.lang.String
   3:          7456         826968  java.lang.Class
   4:         20105         643360  java.util.concurrent.ConcurrentHashMap$Node
   5:          1449         469024  [B
   6:          6951         399280  [Ljava.lang.Object;
   7:          9311         297952  java.util.HashMap$Node
   8:          3122         274736  java.lang.reflect.Method
   9:          2884         269112  [I
  10:          6448         257920  java.util.LinkedHashMap$Entry
  11:          2994         255160  [Ljava.util.HashMap$Node;
  12:         15249         243984  java.lang.Object

.....
.....

複製代碼

如上所示,使用-histo輸出包括序號,實例數,佔用字節數和類名稱。具體說明以下:

  1. instances列:表示當前類有多少個實例。
  2. bytes列:說明當前類的實例總共佔用了多少個字節
  3. class name列:表示的就是當前類的名稱,class name 解讀:
  4. B表明byte
  5. C表明char
  6. D表明double
  7. F表明float
  8. I表明int
  9. J表明long
  10. Z表明boolean
  11. [表明數組,如[I至關於int[]
  12. 對象用[L+類名錶示
  • 把內存狀況dump內存到本地文件
[root@test bin]# jmap -dump:file=./heap.hprof 13680

複製代碼

如上所示,會把堆狀況寫入到當前目錄的heap.hprof文件中,至於如何分析此文件,可使用jhat,但通常實際開發中,不多使用jhat來直接對內存dump文件進行分析,所以再也不對它進行講述。更多的是使用工具MAT,以可視化的方式來查看,後續文章將會對MAT工具的使用進行詳細講解。

6 線程棧查詢工具jstack

6.1 jstack說明

此命令打印指定Java應用的線程堆棧,對於每一個Java幀,將打印完整的類名,方法名,字節代碼索引(BCI)和行號,能夠用於檢測死鎖,線程停頓,進程耗用cpu太高報警問題等排查。

6.2 jstack使用

使用-help參數查看幫助,也可查閱jstack官方文檔說明https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstack.html,幫助說明以下:

[root@test bin]# jstack -help
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>

Options:
    -F  強制dump線程堆棧信息. 用於進程hung住, jstack <pid>命令沒有響應的狀況
    -m  同時打印java和本地(native)線程棧信息,m是mixed mode的簡寫
    -l  打印鎖的額外信息
複製代碼

結合示例java-monitor-example,能夠打印線程信息(通常都會把打印的內容寫入到文件而後再分析),以下:

  • 打印當前線程堆棧信息
[root@test bin]# jstack 13680
2019-08-16 23:18:18
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.51-b03 mixed mode):
"http-nio-8083-Acceptor-0" #39 daemon prio=5 os_prio=0 tid=0x00007f7520698800 nid=0x359a runnable [0x00007f7508bb7000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
	at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
	at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
	- locked <0x00000000f8c85380> (a java.lang.Object)
	at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:448)
	at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:70)
	at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95)
	at java.lang.Thread.run(Thread.java:745)

複製代碼

6.3 線程dump分析

6.3.1 線程狀態

java線程棧使用jstackdump出來後,能夠看到線程的狀態,線程狀態一共分6種,能夠參考官方文檔https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr034.html,下面是它的狀態說明:

  • NEW

線程已經new出來建立了,可是尚未啓動(not yet started),jstack不會打印這個狀態的線程信息。

  • RUNNABLE

正在Java虛擬機下跑任務的線程的狀態,但其實它是隻是表示線程是可運行的(ready)。對於單核的CPU,多個線程在同一時刻,只能運行一個線程,其它的則須要等CPU的調度。

  • BLOCKED

線程處於阻塞狀態,正在等待一個鎖,多個線程共用一個鎖,當某線程正在使用這個鎖進入某個synchronized同步方法塊或者方法,而此線程須要進入這個同步代碼塊,也須要這個鎖,則致使本線程處於阻塞狀態。

  • WAITING

等待狀態,處於等待狀態的線程是因爲執行了3個方法中的任意方法。 1. Object.wait方法,而且沒有使用timeout參數; 2. Thread.join方法,沒有使用timeout參數 3. LockSupport.park方法。 處於waiting狀態的線程會等待另一個線程處理特殊的行爲。一個線程處於等待狀態(wait,一般是在等待其餘線程完成某個操做(notify或者notifyAll)。注意,Object.wait()方法只可以在同步代碼塊中調用。調用了wait()方法後,會釋放鎖。

  • TIMED_WAITING

線程等待指定的時間,對於如下方法的調用,可能會致使線程處於這個狀態:1. Thread.sleep方法 2. Object.wait方法,帶有時間 3. Thread.join方法,帶有時間 4. LockSupport.parkNanos方法,帶有時間 5. LockSupport.parkUntil方法,帶有時間。注意,Thread.sleep方法調用後,它不會釋放鎖,仍然佔用系統資源。

  • TERMINATED

線程停止的狀態,這個線程已經完整地執行了它的任務。

從下面這張圖能夠看出線程狀態的變化狀況:

6.3.2 分析jstack後線程棧內容

從前面使用jstack dump出來信息,咱們須要知道如下幾個信息:

  • "http-nio-8083-Acceptor-0" #39:是線程的名字,所以,通常咱們建立線程時須要設置本身能夠辯識的名字。
  • daemon 表示線程是不是守護線程
  • prio 表示咱們爲線程設置的優先級
  • os_prio 表示的對應的操做系統線程的優先級,因爲並非全部的操做系統都支持線程優先級,因此可能會出現都置爲0的狀況
  • tid 線程的id
  • nid 線程對應的操做系統本地線程id,每個java線程都有一個對應的操做系統線程,它是16進制的,所以通常在操做系統中獲取到線程ID後,須要轉爲16進制,來對應上。
  • java.lang.Thread.State: RUNNABLE 運行狀態,上面已經介紹了線程的狀態,如果WAITING狀態,則括號中的內容說明了致使等待的緣由,如parking說明是由於調用了LockSupport.park方法致使等待。一般的堆棧信息中,都會有lock標記,如- locked <0x00000000f8c85380> (a java.lang.Object)表示正在佔用這個鎖。
  • 對於線程停頓,CPU佔用等問題,能夠重點看一下wait狀態的線程
  • 對於死鎖,在Dump出來的線程棧快照能夠直接報告出Java級別的死鎖。

7 JVM統計數據工具jstat

7.1 jstat說明

jstatJVM Statistics Monitoring Tool,即JVM統計監測工具,包括監測類裝載、內存、垃圾收集、JIT編譯等運行數據,在沒有圖形的服務器上,它是運行期定位虛擬機性能問題的首選工具。

7.2 jstat使用

使用-help參數查看幫助,也可查閱jstat官方文檔說明https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html,幫助說明以下:

[root@test bin]# jstat -help
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

Definitions:
  <option>      An option reported by the -options option
  <vmid>        Virtual Machine Identifier. A vmid takes the following form:
                     <lvmid>[@<hostname>[:<port>]]
                Where <lvmid> is the local vm identifier for the target
                Java virtual machine, typically a process id; <hostname> is
                the name of the host running the target Java virtual machine;
                and <port> is the port number for the rmiregistry on the
                target host. See the jvmstat documentation for a more complete
                description of the Virtual Machine Identifier.
  <lines>       Number of samples between header lines.
  <interval>    Sampling interval. The following forms are allowed:
                    <n>["ms"|"s"]
                Where <n> is an integer and the suffix specifies the units as
                milliseconds("ms") or seconds("s"). The default units are "ms".
  <count>       Number of samples to take before terminating.
  -J<flag>      Pass <flag> directly to the runtime system.
複製代碼

以上所示,vmidintervalcount分別是進程號,打印間隔時間(s或ms),打印次數,其中option參數主要是如下(也可使用命令jstat -option查看):

  • -class 統計class loader行爲信息 ,如總共加載了多少個類
  • -compile 統計HotSpot Just-in-Time編譯器的行爲
  • -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編譯方法統計

通常咱們使用-class-gc-gccause-gcutil比較多,主要用於來分析類和堆使用狀況及gc狀況。

7.3 監測JVM的GC狀況

以上述的示例工程java-monitor-example爲例,裏面包含了一個函數來測試內存溢出(使用一個數組,循環建立對象,直到內存溢出)。使用jstat -gc 13680 1000即每秒監測一次,調用/monitor/user/oom接口後,即看到堆和GC變化狀況。爲方便查看,我把輸出放到sublime中顯示,以下所示:

日誌輸出OOM報錯:

以上輸出的內容,每列的說明以下:

  • S0C 當前年輕代中第一個survivor(s0)的總容量 (KB).
  • S1C 當前年輕代中第一個survivor(s1)的總容量 (KB).
  • S0U s0已使用的容量 (KB).
  • S1U s1已使用的容量 (KB).
  • EC 當前年輕代中eden區總容量 (KB).
  • EU eden區已經使用的容量 (KB).
  • OC 年老代的容量總容量 (KB).
  • OU 年老代已使用容量(KB).
  • MC 當前 Metaspace總容量(KB).
  • MU 當前 Metaspace已使用容量 (KB).
  • CCSC Compressed class容量大小
  • CCSU Compressed class已使用容量
  • YGC 從應用啓動時到如今,年輕代young generation 發生GC Events的總次數.
  • YGCT 從應用啓動時到如今, 年輕代Young generation 垃圾回收的總耗時.
  • FGC 從應用啓動時到如今, full GC事件總次數.
  • FGCT 從應用啓動時到如今, Full sc總耗時.GCT 從應用啓動時到如今, 垃圾回收總時間. - GCT GCT=YGCT+FGCT

從以上輸出第6行能夠看出,ECEUOCOU表示年輕代、年老代的內存都已經用完(與容量數值相等),發生OOM。這時,則須要採起措施,增大內存(-Xmx參數)或者找到致使OOM的代碼進行修改。

8 總結

針對java應用的監測,本文對jdk提供自身提供的命令行工具進行了說明和使用的介紹,完整的描述了查看java應用進程,查看啓動參數,查看內存狀況,查看線程狀況,查看內存統計狀況等,主要是jpsjinfojmapjstackjstat5個工具,並結合實例,但願學習java開發人員都能掌握這些技術,在監測java應用時,能夠從容面對如OOM,CPU高,線程停頓等問題。

參考資料

相關閱讀

相關文章
相關標籤/搜索