jdk命令行工具系列——檢視閱讀(二)

jmap——java內(nèi)存映射工具

jdk安裝后會自帶一些小工具葬荷,jmap命令(Memory Map for Java)是其中之一。主要用于打印指定Java進程(或核心文件纽帖、遠程調(diào)試服務器)的共享對象內(nèi)存映射或堆內(nèi)存細節(jié)宠漩。

jmap命令可以獲得運行中的jvm的堆的快照,從而可以離線分析堆懊直,以檢查內(nèi)存泄漏扒吁,檢查一些嚴重影響性能的大對象的創(chuàng)建,檢查系統(tǒng)中什么對象最多室囊,各種對象所占內(nèi)存的大小等等雕崩。可以使用jmap生成Heap Dump波俄。

如果不想使用jmap命令晨逝,要想獲取Java堆轉儲快照還有一些比較“暴力”的手段:譬如在前面用過的 -XX:+HeapDumpOnOutOfMemoryError參數(shù),可以讓虛擬機在OOM異常出現(xiàn)之后自動生成dump文件懦铺,通過-XX:+HeapDumpOnCtrlBreak參數(shù)可以使用[ctrl]+[Break]鍵讓虛擬機生成dump文件,又或者在Linux系統(tǒng)下通過Kill -3 命令發(fā)送進程退出信息“恐嚇”一下虛擬機支鸡,也能拿到dump文件冬念。

jmap的作用并不僅僅是為了獲取dump文件,他還可以查詢finalize執(zhí)行隊列牧挣,java堆和永久代的詳細信息急前,如空間使用率、當前用的是哪種收集器等瀑构。

jmap命令格式

[root@ady01 ~]# jmap
Usage:
    jmap [option] <pid>
        (to connect to running process)
    jmap [option] <executable <core>
        (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

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

主要選項:

選項 作用
-dump 生成java堆轉儲快照裆针,格式為:-dump:[live,]format=b,file=<filename>,其中l(wèi)ive子參數(shù)說明是否只dump出存活對象
-finalizerinfo 顯示在F-Queue中等待Finalizer線程執(zhí)行finalize方法的對象,只在linux/solaris平臺下有效
-heap 顯示堆詳細信息寺晌,如使用哪種回收期世吨、參數(shù)配置、分帶狀況等呻征,只在linux/solaris平臺下有效
-histo 顯示堆中對象統(tǒng)計信息耘婚,包括類、實例數(shù)量和合計容量
-permstat 以ClassLoader為統(tǒng)計口徑顯示永久代內(nèi)存狀況陆赋,只在linux/solaris平臺下有效
-F 當虛擬機進程對-dump選項沒有響應時沐祷,可以使用這個選項強制生成dump快照嚷闭,只在linux/solaris平臺下有效

jmap -dump:生成java堆轉儲快照

生成java對轉存快照,格式:jmap -dump:[live,]format=b,file=文件名 <pid>

C:\Users\Think>jmap -dump:live,format=b,file=D:/dumptest.hprof 13984
Dumping heap to D:\dumptest.hprof ...
Heap dump file created

可以使用jdk提供的jvisualvm.exe查看hprof文件

jmap -heap:顯示堆詳細信息

顯示堆詳細信息赖临。

注意:使用時報錯排查原因是由于機器上安裝了多個jdk導致的胞锰。所以使用時要指定路徑。

E:\java8\jdk\bin\jmap -heap 13984

命令格式:jmap -heap <pid>

[root@ady01 ~]# jmap -heap 25867
Attaching to process ID 25867, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13

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

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 4164943872 (3972.0MB)
   NewSize                  = 87031808 (83.0MB)
   MaxNewSize               = 1388314624 (1324.0MB)
   OldSize                  = 175112192 (167.0MB)
   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 = 47710208 (45.5MB)
   used     = 2632072 (2.5101394653320312MB)
   free     = 45078136 (42.98986053466797MB)
   5.516790033696772% used
From Space:
   capacity = 1048576 (1.0MB)
   used     = 770128 (0.7344512939453125MB)
   free     = 278448 (0.2655487060546875MB)
   73.44512939453125% used
To Space:
   capacity = 524288 (0.5MB)
   used     = 0 (0.0MB)
   free     = 524288 (0.5MB)
   0.0% used
PS Old Generation
   capacity = 220200960 (210.0MB)
   used     = 98595728 (94.02821350097656MB)
   free     = 121605232 (115.97178649902344MB)
   44.77533976236979% used

38803 interned Strings occupying 4463232 bytes.

jmap -histo:顯示堆中對象統(tǒng)計信息

顯示堆中對象統(tǒng)計信息兢榨,包括類嗅榕、實例數(shù)量和合計容量

命令格式:jmap -histo[:live] <pid>

C:\Users\Think>jmap -histo 28252

 num     #instances         #bytes  class name
----------------------------------------------
   1:        309006       16963968  [C
   2:          1081        7275840  [I
   3:         41164        3156952  [B
   4:         90125        2163000  java.lang.String
   5:         21000         672000  java.util.UUID
   6:         21000         336000  com.jvm.test8.Test8$User
   7:         21000         336000  com.jvm.test8.Test8$User$UserBuilder
   8:           799         300072  [Ljava.lang.Object;
   9:          7557         181368  java.lang.StringBuilder
  10:           772          87704  java.lang.Class
  11:          1026          65664  sun.nio.fs.WindowsFileAttributes
  12:          1026          49248  sun.nio.fs.WindowsPath$WindowsPathWithAttributes
  13:           837          33480  java.util.TreeMap$Entry
  14:          1032          33024  java.lang.ref.WeakReference
  15:           775          31000  sun.nio.fs.WindowsPath
  16:          1028          24672  sun.nio.fs.WindowsPathParser$Result
  17:           424          13568  java.io.File
  18:           168          12096  java.lang.reflect.Field
  19:           299          11960  java.util.LinkedHashMap$Entry
  20:           323          11200  [Ljava.lang.String;
  21:           173          11072  java.net.URL
  22:           341          10912  sun.misc.FDBigInteger
  23:           312           9984  java.util.Hashtable$Entry
  24:            66           8992  [Ljava.util.HashMap$Node;
  25:           267           8544  java.util.HashMap$Node
  26:           195           7800  java.lang.ref.Finalizer
  27:           264           6336  java.lang.StringBuffer
  28:           121           4840  java.lang.ref.SoftReference
  29:            29           4816  [Ljava.util.Hashtable$Entry;
  30:            50           4800  java.util.jar.JarFile$JarFileEntry
  31:           105           4552  [[C
  32:            53           4240  [Ljava.util.WeakHashMap$Entry;
  33:            74           4144  sun.misc.URLClassPath$JarLoader
  34:           258           4128  java.lang.Integer
  35:            50           4000  java.util.zip.ZipEntry
  36:            79           3792  java.net.NetworkInterface
  37:           150           3600  java.net.Parts
  38:           111           3552  java.util.concurrent.ConcurrentHashMap$Node
  39:           134           3216  java.security.Provider$ServiceKey
  40:            50           3200  java.util.jar.JarFile
  41:            55           3080  sun.nio.cs.UTF_8$Encoder
  42:             8           3008  java.lang.Thread
  43:            62           2976  java.util.HashMap
  44:            51           2856  java.util.zip.ZipFile$ZipFileInputStream
  45:           114           2736  java.io.ExpiringCache$Entry
  46:            53           2544  java.util.WeakHashMap
  47:            30           2400  java.lang.reflect.Constructor
  48:            56           2240  java.util.WeakHashMap$Entry
  49:            39           2184  java.util.zip.ZipFile$ZipFileInflaterInputStream

疑問:

Q: 如何dump堆快照,如何使用jvisualvm.exe查看java進程上dump下來的hprof文件色乾?

A: 首選誊册,我們dump堆快照可以使用jmap命令手動dump下想要獲取的堆快照。格式如下:

jmap -dump:[live,]format=b,file=文件名 <pid>

jmap -dump:live,format=b,file=D:/1.hprof 24956

其次暖璧,如果不想使用jmap命令案怯,要想獲取Java堆轉儲快照還有一些比較“暴力”的手段:譬如在前面用過的 -XX:+HeapDumpOnOutOfMemoryError參數(shù),可以讓虛擬機在OOM異常出現(xiàn)之后自動生成dump文件澎办,通過-XX:+HeapDumpOnCtrlBreak參數(shù)可以使用[ctrl]+[Break]鍵讓虛擬機生成dump文件嘲碱,又或者在Linux系統(tǒng)下通過Kill -3 命令發(fā)送進程退出信息“恐嚇”一下虛擬機,也能拿到dump文件局蚀。

windows使用jvisualvm.exe查看java進程上dump操作如下:

1麦锯、進入jdk按照的bin目錄打開jvisualvm.exe。

E:\java8\jdk\bin

2琅绅、點擊文件裝入取選取我們dump下的文件位置扶欣。注意要更改裝入的文件類型為.hprof文件。

3千扶、切換到類選型就可以查看當前dump文件中存活的類占比較大的是什么對象料祠。從而進一步分析內(nèi)存溢出的原因。

jhat——虛擬機堆轉儲快照分析工具——一般很少用這個澎羞,而是用集成工具

jhat也是jdk內(nèi)置的工具之一髓绽。主要是用來分析java堆的命令,可以將堆中的對象以html的形式顯示出來妆绞,包括對象的數(shù)量顺呕,大小等等,并支持對象查詢語言括饶。

使用jmap等方法生成java的堆文件后株茶,使用其進行分析

示例:

1.運行代碼:

package com.jvm.test8;

import lombok.*;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class Test8 {
    @Getter
    @Setter
    @ToString
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class User {
        private String name;
    }

    public static void main(String[] args) throws InterruptedException {
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 3000; i++) {
            for (int j = 0; j < 1000; j++) {
                list.add(User.builder().name(UUID.randomUUID().toString()).build());
            }
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

2.導出程序執(zhí)行的堆信息——jmap

F:\fcargitnew\hellospringboot>jps -l
13984
12468 sun.tools.jps.Jps
6324 org.jetbrains.jps.cmdline.Launcher
6908 com.self.test.Test2
8908 org.jetbrains.idea.maven.server.RemoteMavenServer

F:\fcargitnew\hellospringboot>jmap -dump:live,format=b,file=D:/dump.hprof 6908
Dumping heap to D:\dump.hprof ...
Heap dump file created

3.使用jhat分析堆文件

F:\fcargitnew\hellospringboot>jmap -dump:live,format=b,file=D:/dump.hprof 6908
Dumping heap to D:\dump.hprof ...
Heap dump file created

F:\fcargitnew\hellospringboot>jhat D:/dump.hprof
Reading from D:/dump.hprof...
Dump file created Mon Oct 19 11:21:10 CST 2020
Snapshot read, resolving...
Resolving 179681 objects...
Chasing references, expect 35 dots...................................
Eliminating duplicate references...................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

4.查看html

分析內(nèi)存泄露問題主要會用到“Show heap histogram”“”和“OQL”,前者可以找到內(nèi)存中總容量最大的對象巷帝,后者是標準的對象查詢語言忌卤,使用類似于SQL的語法對內(nèi)存對象進行查詢統(tǒng)計。

  • 顯示出堆中所包含的所有的類
  • 從根集能引用到的對象
  • 顯示所有類(包括平臺)的實例計數(shù)
  • 堆實例的分布表
  • 執(zhí)行對象查詢語句

輸入內(nèi)容如:

查詢長度大于100的字符串

select s from java.lang.String s where s.count > 100

詳細的OQL可點擊上圖的“OQL help”

jhat中的OQL(對象查詢語言) 楞泼,文檔可以查看:http://localhost:7000/oqlhelp/
如果需要根據(jù)某些條件來過濾或查詢堆的對象驰徊,這是可能的笤闯,可以在jhat的html頁面中執(zhí)行OQL,來查詢符合條件的對象

基本語法: 
select <javascript expression to select>
[from [instanceof] <class name> <identifier>]
[where <javascript boolean expression to filter>]

解釋: 
(1)class name是java類的完全限定名棍厂,如:java.lang.String, java.util.ArrayList, [C是char數(shù)組, [Ljava.io.File是java.io.File[]
(2)類的完全限定名不足以唯一的辨識一個類颗味,因為不同的ClassLoader載入的相同的類,它們在jvm中是不同類型的
(3)instanceof表示也查詢某一個類的子類牺弹,如果不明確instanceof浦马,則只精確查詢class name指定的類
(4)from和where子句都是可選的
(5)java域表示:obj.field_name;java數(shù)組表示:array[index]

舉例: 
(1)查詢長度大于100的字符串
select s from java.lang.String s where s.count > 100

(2)查詢長度大于256的數(shù)組
select a from [I a where a.length > 256

(3)顯示匹配某一正則表達式的字符串
select a.value.toString() from java.lang.String s where /java/(s.value.toString())

(4)顯示所有文件對象的文件路徑
select file.path.value.toString() from java.io.File file

(5)顯示所有ClassLoader的類名
select classof(cl).name from instanceof java.lang.ClassLoader cl

(6)通過引用查詢對象
select o from instanceof 0xd404d404 o

built-in對象 -- heap 
(1)heap.findClass(class name) -- 找到類
select heap.findClass("java.lang.String").superclass

(2)heap.findObject(object id) -- 找到對象
select heap.findObject("0xd404d404")

(3)heap.classes -- 所有類的枚舉
select heap.classes

(4)heap.objects -- 所有對象的枚舉
select heap.objects("java.lang.String")

(5)heap.finalizables -- 等待垃圾收集的java對象的枚舉

(6)heap.livepaths -- 某一對象存活路徑
select heaplivepaths(s) from java.lang.String s

(7)heap.roots -- 堆根集的枚舉

辨識對象的函數(shù) 
(1)classof(class name) -- 返回java對象的類對象
select classof(cl).name from instanceof java.lang.ClassLoader cl

(2)identical(object1,object2) -- 返回是否兩個對象是同一個實例
select identical(heap.findClass("java.lang.String").name, heap.findClass("java.lang.String").name)

(3)objectid(object) -- 返回對象的id
select objectid(s) from java.lang.String s

(4)reachables -- 返回可從對象可到達的對象
select reachables(p) from java.util.Properties p -- 查詢從Properties對象可到達的對象
select reachables(u, "java.net.URL.handler") from java.net.URL u -- 查詢從URL對象可到達的對象张漂,但不包括從URL.handler可到達的對象

(5)referrers(object) -- 返回引用某一對象的對象
select referrers(s) from java.lang.String s where s.count > 100

(6)referees(object) -- 返回某一對象引用的對象
select referees(s) from java.lang.String s where s.count > 100

(7)refers(object1,object2) -- 返回是否第一個對象引用第二個對象
select refers(heap.findObject("0xd4d4d4d4"),heap.findObject("0xe4e4e4e4"))

(8)root(object) -- 返回是否對象是根集的成員
select root(heap.findObject("0xd4d4d4d4")) 

(9)sizeof(object) -- 返回對象的大小
select sizeof(o) from [I o

(10)toHtml(object) -- 返回對象的html格式
select "<b>" + toHtml(o) + "</b>" from java.lang.Object o

(11)選擇多值
select {name:t.name?t.name.toString():"null",thread:t} from instanceof java.lang.Thread t

數(shù)組晶默、迭代器等函數(shù) 
(1)concat(enumeration1,enumeration2) -- 將數(shù)組或枚舉進行連接
select concat(referrers(p),referrers(p)) from java.util.Properties p

(2)contains(array, expression) -- 數(shù)組中元素是否滿足某表達式
select p from java.util.Properties where contains(referres(p), "classof(it).name == 'java.lang.Class'")
返回由java.lang.Class引用的java.util.Properties對象
built-in變量
it -- 當前的迭代元素
index -- 當前迭代元素的索引
array -- 被迭代的數(shù)組

(3)count(array, expression) -- 滿足某一條件的元素的數(shù)量
select count(heap.classes(), "/java.io./(it.name)")

(4)filter(array, expression) -- 過濾出滿足某一條件的元素
select filter(heap.classes(), "/java.io./(it.name)")

(5)length(array) -- 返回數(shù)組長度
select length(heap.classes())

(6)map(array,expression) -- 根據(jù)表達式對數(shù)組中的元素進行轉換映射
select map(heap.classes(),"index + '-->' + toHtml(it)")

(7)max(array,expression) -- 最大值, min(array,expression)
select max(heap.objects("java.lang.String"),"lhs.count>rhs.count")
built-in變量
lhs -- 左邊元素
rhs -- 右邊元素

(8)sort(array,expression) -- 排序
select sort(heap.objects('[C'),'sizeof(lhs)-sizeof(rhs)')

(9)sum(array,expression) -- 求和
select sum(heap.objects('[C'),'sizeof(it)')

(10)toArray(array) -- 返回數(shù)組

(11)unique(array) -- 唯一化數(shù)組

jstack——java棧跟蹤工具

jstack介紹

jstack(stack trace for java)是java虛擬機自帶的一種堆棧跟蹤工具。jstack用于打印出給定的java進程ID或core file或遠程調(diào)試服務的Java堆棧信息航攒,如果是在64位機器上磺陡,需要指定選項"-J-d64",Windows的jstack使用方式只支持以下的這種方式:

jstack [-l] pid

主要分為兩個功能:

  1. 針對活著的進程做本地的或遠程的線程dump
  2. 針對core文件做線程dump

jstack用于生成java虛擬機當前時刻的線程快照漠畜。

線程快照是當前java虛擬機內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合币他,生成線程快照的主要目的是定位線程出現(xiàn)長時間停頓的原因,如線程間死鎖憔狞、死循環(huán)蝴悉、請求外部資源導致的長時間等待等。

線程出現(xiàn)停頓的時候通過jstack來查看各個線程的調(diào)用堆棧瘾敢,就可以知道沒有響應的線程到底在后臺做什么事情拍冠,或者等待什么資源。

如果java程序崩潰生成core文件簇抵,jstack工具可以用來獲得core文件的java stack和native stack的信息倦微,從而可以輕松地知道java程序是如何崩潰和在程序何處發(fā)生問題。

另外正压,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現(xiàn)在運行的java程序呈現(xiàn)hung的狀態(tài)责球,jstack是非常有用的焦履。

So,jstack命令主要用來查看Java線程的調(diào)用堆棧的雏逾,可以用來分析線程問題(如死鎖)嘉裤。

線程狀態(tài)

想要通過jstack命令來分析線程的情況的話,首先要知道線程都有哪些狀態(tài)栖博,下面這些狀態(tài)是我們使用jstack命令查看線程堆棧信息時可能會看到的線程的6種狀態(tài):

  1. NEW:未啟動的屑宠。不會出現(xiàn)在Dump中。
  2. RUNNABLE:在虛擬機內(nèi)執(zhí)行的仇让。運行中狀態(tài)典奉,可能里面還能看到locked字樣躺翻,表明它獲得了某把鎖。
  3. BLOCKED:受阻塞并等待監(jiān)視器鎖卫玖。被某個鎖(synchronizers)給block住了公你。
  4. WATING:無限期等待另一個線程執(zhí)行特定操作。等待某個condition或monitor發(fā)生假瞬,一般停留在park(), wait(),sleep(),join() 等語句里陕靠。
  5. TIMED_WATING:有時限的等待另一個線程的特定操作。和WAITING的區(qū)別是wait() 等語句加上了時間限制 wait(timeout)脱茉。
  6. TERMINATED:已退出的剪芥。

關于線程狀態(tài),具體也可以查看:java.lang.Thread.State類琴许。

Monitor(監(jiān)視器)

在多線程的 JAVA程序中税肪,實現(xiàn)線程之間的同步,就要說說 Monitor虚吟。 Monitor是 Java中用以實現(xiàn)線程之間的互斥與協(xié)作的主要手段寸认,它可以看成是對象或者 Class的鎖。每一個對象都有串慰,也僅有一個 monitor偏塞。下面這個圖,描述了線程和 Monitor之間關系邦鲫,以 及線程的狀態(tài)轉換圖:

  1. 進入?yún)^(qū)(Entrt Set):表示線程通過synchronized要求獲取對象的鎖灸叼。如果對象未被鎖住(即獲得到鎖)庆捺,則進入擁有者古今;否則則在進入?yún)^(qū)等待。一旦對象鎖被其他線程釋放滔以,立即參與競爭捉腥。
  2. 擁有者(The Owner):表示某一線程成功競爭到對象鎖。
  3. 等待區(qū)(Wait Set):表示線程通過對象的wait方法,釋放對象的鎖,并在等待區(qū)等待被喚醒你画。

從圖中可以看出抵碟,一個 Monitor在某個時刻,只能被一個線程擁有坏匪,該線程就是 “Active Thread”拟逮,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set”和 “Wait Set”里面等候适滓。在 “Entry Set”中等待的線程狀態(tài)是 “Waiting for monitor entry”敦迄,而在“Wait Set”中等待的線程狀態(tài)是 “in Object.wait()”。 先看 “Entry Set”里面的線程。我們稱被 synchronized保護起來的代碼段為臨界區(qū)罚屋。當一個線程申請進入臨界區(qū)時苦囱,它就進入了 “Entry Set”隊列。對應的 code就像:

synchronized(obj) {
    //.........
}

調(diào)用修飾

表示線程在方法調(diào)用時,額外的重要的操作沿后。線程Dump分析的重要信息沿彭。修飾上方的方法調(diào)用蛾找。

locked <地址> 目標:使用synchronized申請對象鎖成功,監(jiān)視器的擁有者奸披。

waiting to lock <地址> 目標:使用synchronized申請對象鎖未成功,在進入?yún)^(qū)等待囱皿。

waiting on <地址> 目標:使用synchronized申請對象鎖成功后,釋放鎖并在等待區(qū)等待独悴。

parking to wait for <地址> 目標:park是基本的線程阻塞原語,不通過監(jiān)視器在對象上阻塞浑厚。隨concurrent包會出現(xiàn)的新的機制,不synchronized體系不同业簿。

locked

at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement

通過synchronized關鍵字赫悄,成功獲取到了對象的鎖仙畦,成為監(jiān)視器的擁有者撼唾,在臨界區(qū)內(nèi)操作廉邑。對象鎖是可以線程重入的。

waiting to lock

at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
- waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
at com.jiuqi.dna.core.impl.ContextImpl.find
at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo

通過synchronized關鍵字倒谷,沒有獲取到了對象的鎖蛛蒙,線程在監(jiān)視器的進入?yún)^(qū)等待。在調(diào)用棧頂出現(xiàn)渤愁,線程狀態(tài)為Blocked牵祟。

waiting on

at java.lang.Object.wait(Native Method)
- waiting on <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
- locked <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run

通過synchronized關鍵字,成功獲取到了對象的鎖后,調(diào)用了wait方法,進入對象的等待區(qū)等待。在調(diào)用棧頂出現(xiàn),線程狀態(tài)為WAITING或TIMED_WATING抖格。

parking to wait for

park是基本的線程阻塞原語诺苹,不通過監(jiān)視器在對象上阻塞。隨concurrent包會出現(xiàn)的新的機制雹拄,與synchronized體系不同收奔。

線程動作

線程狀態(tài)產(chǎn)生的原因:

  1. runnable:狀態(tài)一般為RUNNABLE。
  2. in Object.wait():等待區(qū)等待滓玖,狀態(tài)為WAITING或TIMED_WAITING坪哄。
  3. waiting for monitor entry:進入?yún)^(qū)等待,狀態(tài)為BLOCKED势篡。
  4. waiting on condition:等待區(qū)等待损姜、被park。
  5. sleeping:休眠的線程殊霞,調(diào)用了Thread.sleep()。

Wait on condition 該狀態(tài)出現(xiàn)在線程等待某個條件的發(fā)生汰蓉。具體是什么原因绷蹲,可以結合 stack trace來分析。 最常見的情況就是線程處于sleep狀態(tài),等待被喚醒祝钢。 常見的情況還有等待網(wǎng)絡IO:在java引入nio之前比规,對于每個網(wǎng)絡連接,都有一個對應的線程來處理網(wǎng)絡的讀寫操作拦英,即使沒有可讀寫的數(shù)據(jù)蜒什,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費疤估,而且給操作系統(tǒng)的線程調(diào)度也帶來壓力灾常。在 NIO里采用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高铃拇。 正等待網(wǎng)絡讀寫钞瀑,這可能是一個網(wǎng)絡瓶頸的征兆。因為網(wǎng)絡阻塞導致線程無法執(zhí)行慷荔。一種情況是網(wǎng)絡非常忙雕什,幾乎消耗了所有的帶寬,仍然有大量數(shù)據(jù)等待網(wǎng)絡讀 寫显晶;另一種情況也可能是網(wǎng)絡空閑贷岸,但由于路由等問題,導致包無法正常的到達磷雇。所以要結合系統(tǒng)的一些性能觀察工具來綜合分析偿警,比如 netstat統(tǒng)計單位時間的發(fā)送包的數(shù)目,如果很明顯超過了所在網(wǎng)絡帶寬的限制 ; 觀察 cpu的利用率倦春,如果系統(tǒng)態(tài)的CPU時間户敬,相對于用戶態(tài)的 CPU時間比例較高;如果程序運行在 Solaris 10平臺上睁本,可以用 dtrace工具看系統(tǒng)調(diào)用的情況尿庐,如果觀察到 read/write的系統(tǒng)調(diào)用的次數(shù)或者運行時間遙遙領先;這些都指向由于網(wǎng)絡帶寬所限導致的網(wǎng)絡瓶頸呢堰。

jstack命令格式

jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP

常用參數(shù)說明

1)options:

executable Java executable from which the core dump was produced.(可能是產(chǎn)生core dump的java可執(zhí)行程序)

core : 將被打印信息的core dump文件

remote-hostname-or-IP :遠程debug服務的主機名或ip

server-id :唯一id,假如一臺主機上多個遠程debug服務

2)基本參數(shù):

  1. -F :當’jstack [-l] pid’沒有響應的時候抄瑟,強制打印線程堆棧信息,一般情況不需要使用
  2. -l :長列表. 打印關于鎖的附加信息枉疼,例如屬于java.util.concurrent的ownable synchronizers列表皮假,會使得JVM停頓得長久得多(可能會差很多倍,比如普通的jstack可能幾毫秒和一次GC沒區(qū)別骂维,加了-l 就是近一秒的時間)惹资,-l 建議不要用,一般情況不需要使用
  3. -m : 打印java和native c/c++框架的所有棧信息.可以打印JVM的堆棧航闺,顯示上Native的棧幀褪测,一般應用排查不需要使用
  4. -h | -help :打印幫助信息
  5. pid :需要被打印配置信息的java進程id猴誊,可以用jps查詢

使用示例

jstack pid

~$ jps -ml
org.apache.catalina.startup.Bootstrap 
~$ jstack 5661
2013-04-16 21:09:27
Full thread dump Java HotSpot(TM) Server VM (20.10-b01 mixed mode):

"Attach Listener" daemon prio=10 tid=0x70e95400 nid=0x2265 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"http-bio-8080-exec-20" daemon prio=10 tid=0x08a35800 nid=0x1d42 waiting on condition [0x70997000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x766a27b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)
    at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:399)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:947)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
    at java.lang.Thread.run(Thread.java:662)
........

死循環(huán)

  1. 寫個死循環(huán)代碼
    package com.jvm.jstack;

     /**
      * <a >Java干貨鋪子,只生產(chǎn)干貨,公眾號:javacode2018</a>
      */
     public class Demo1 {
         public static void main(String[] args) {
             while (true) {
             }
         }
     }
    
  2. 運行代碼

  3. cmd中執(zhí)行jps查看程序進程id
    F:\fcargitnew\hellospringboot>jps
    13984
    11060 Test2
    12916 Launcher
    396 Jps
    8908 RemoteMavenServer

    進程id為 11060

  4. 輸入jstack 11060命令侮措,找到跟我們自己代碼相關的線程懈叹,如下為main線程,處于runnable狀態(tài)分扎,在main方法的第8行澄成,也就是我們死循環(huán)的位置.
    jstack 11060

    "main" #1 prio=5 os_prio=0 tid=0x0000000002a37000 nid=0x2c50 runnable [0x000000000282f000]
       java.lang.Thread.State: RUNNABLE
            at com.self.test.Test2.main(Test2.java:46)
    

Object.wait()情況

執(zhí)行下列代碼:

package com.jvm.jstack;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * <a >Java干貨鋪子,只生產(chǎn)干貨,公眾號:javacode2018</a>
 */
public class Demo2 {
    static class TestTask implements Runnable {
        @Override
        public void run() {

            synchronized (this) {
                try {
                    //等待被喚醒
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        ExecutorService ex = Executors.newFixedThreadPool(1);
        ex.execute(new TestTask());
    }
}


"From DemoThreadFactory's 訂單創(chuàng)建組-Worker-1" #11 prio=5 os_prio=0 tid=0x000000001e476000 nid=0x13f0 in Object.wait() [0x000000001f12e000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b798240> (a com.self.test.Test3$TestTask)
        at java.lang.Object.wait(Object.java:502)
        at com.self.test.Test3$TestTask.run(Test3.java:28)
        - locked <0x000000076b798240> (a com.self.test.Test3$TestTask)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

死鎖情況

  1. 寫個死鎖的例子
    public class TestDeadLock {

        private static Object obj1 = new Object();
        private static Object obj2 = new Object();
    
        public static void main(String[] args) {
            //自定義飽和策略
            ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 600,
                    TimeUnit.SECONDS, new LinkedBlockingQueue<>(5),
                    new DemoThreadFactory("訂單創(chuàng)建組"), new ThreadPoolExecutor.AbortPolicy());
            // 起10個線程
            for (int i = 0; i < 10; i++) {
                int order = i % 2 == 0 ? 1 : 0;
                executor.execute(new MyRunnable(order));
            }
        }
    
        static class MyRunnable implements Runnable{
            private int order;
    
            public MyRunnable( int order) {
                this.order = order;
            }
    
            public void test1() throws InterruptedException {
                synchronized (obj1) {
                    synchronized (obj2) {
                        System.out.println("test1畏吓。墨状。。");
                    }
                }
            }
    
            public void test2() throws InterruptedException {
                synchronized (obj2) {
                    synchronized (obj1) {
                        System.out.println("test2庵佣。歉胶。。");
                    }
                }
            }
    
            @Override
            public void run() {
                while (true) {
                    try {
                        if (this.order == 1) {
                            this.test1();
                        } else {
                            this.test2();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  2. 運行上面代碼產(chǎn)生死鎖.

  3. 我們先通過jsp查找到程序的進程巴粪,然后通過jstack查看線程堆棧通今,很快就可以發(fā)現(xiàn)死鎖
    Found one Java-level deadlock:
    =============================
    "From DemoThreadFactory's 訂單創(chuàng)建組-Worker-10":
    waiting to lock monitor 0x000000001c46dfa8 (object 0x000000076b77cf68, a java.lang.Object),
    which is held by "From DemoThreadFactory's 訂單???建組-Worker-4"
    "From DemoThreadFactory's 訂單創(chuàng)建組-Worker-4":
    waiting to lock monitor 0x000000001c46f448 (object 0x000000076b77cf58, a java.lang.Object),
    which is held by "From DemoThreadFactory's 訂單創(chuàng)建組-Worker-5"
    "From DemoThreadFactory's 訂單創(chuàng)建組-Worker-5":
    waiting to lock monitor 0x000000001c46dfa8 (object 0x000000076b77cf68, a java.lang.Object),
    which is held by "From DemoThreadFactory's 訂單創(chuàng)建組-Worker-4"

    Java stack information for the threads listed above:
    ===================================================
    "From DemoThreadFactory's 訂單創(chuàng)建組-Worker-10":
            at com.self.test.TestDeadLock$MyRunnable.test2(TestDeadLock.java:55)
            - waiting to lock <0x000000076b77cf68> (a java.lang.Object)
            at com.self.test.TestDeadLock$MyRunnable.run(TestDeadLock.java:68)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
            at java.lang.Thread.run(Thread.java:748)
    "From DemoThreadFactory's 訂單創(chuàng)建組-Worker-4":
            at com.self.test.TestDeadLock$MyRunnable.test2(TestDeadLock.java:56)
            - waiting to lock <0x000000076b77cf58> (a java.lang.Object)
            - locked <0x000000076b77cf68> (a java.lang.Object)
            at com.self.test.TestDeadLock$MyRunnable.run(TestDeadLock.java:68)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
            at java.lang.Thread.run(Thread.java:748)
    "From DemoThreadFactory's 訂單創(chuàng)建組-Worker-5":
            at com.self.test.TestDeadLock$MyRunnable.test1(TestDeadLock.java:48)
            - waiting to lock <0x000000076b77cf68> (a java.lang.Object)
            - locked <0x000000076b77cf58> (a java.lang.Object)
            at com.self.test.TestDeadLock$MyRunnable.run(TestDeadLock.java:66)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
            at java.lang.Thread.run(Thread.java:748)
    
    Found 1 deadlock.
    

等待io

  1. 運行代碼
    public class TestIO {
    public static void main(String[] args) throws IOException {
    InputStream is = System.in;
    int i = is.read();
    System.out.println("exit。");
    }
    }

  2. 和上面一樣肛根,jps獲取進程辫塌,jstack獲取線程堆棧信息
    //長列表. 打印關于鎖的附加信息
    jstack -l 9168

    "main" #1 prio=5 os_prio=0 tid=0x00000000029b7000 nid=0xee0 runnable [0x000000000281f000]
       java.lang.Thread.State: RUNNABLE
            at java.io.FileInputStream.readBytes(Native Method)
            at java.io.FileInputStream.read(FileInputStream.java:255)
            at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
            at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
            - locked <0x000000076b4614f8> (a java.io.BufferedInputStream)
            at com.self.test.TestIO.main(TestIO.java:19)
    
       Locked ownable synchronizers:
            - None
    

疑問:

Q: Wait on condition 該狀態(tài)出現(xiàn)在線程等待某個條件的發(fā)生。具體是什么原因派哲,可以結合 stack trace來分析臼氨。 最常見的情況就是線程處于sleep狀態(tài),等待被喚醒芭届。 這應該是wait狀態(tài)储矩,等待被喚醒,而不是sleep狀態(tài)吧褂乍?

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末持隧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子逃片,更是在濱河造成了極大的恐慌屡拨,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褥实,死亡現(xiàn)場離奇詭異呀狼,居然都是意外死亡,警方通過查閱死者的電腦和手機损离,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門哥艇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人僻澎,你說我怎么就攤上這事她奥∥驮觯” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵哩俭,是天一觀的道長。 經(jīng)常有香客問我拳恋,道長凡资,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任谬运,我火速辦了婚禮隙赁,結果婚禮上,老公的妹妹穿的比我還像新娘梆暖。我一直安慰自己伞访,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布轰驳。 她就那樣靜靜地躺著厚掷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪级解。 梳的紋絲不亂的頭發(fā)上冒黑,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音勤哗,去河邊找鬼抡爹。 笑死,一個胖子當著我的面吹牛芒划,可吹牛的內(nèi)容都是我干的冬竟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼民逼,長吁一口氣:“原來是場噩夢啊……” “哼泵殴!你這毒婦竟也來了?” 一聲冷哼從身側響起缴挖,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤袋狞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后映屋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苟鸯,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年棚点,在試婚紗的時候發(fā)現(xiàn)自己被綠了早处。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘫析,死狀恐怖砌梆,靈堂內(nèi)的尸體忽然破棺而出默责,到底是詐尸還是另有隱情,我是刑警寧澤咸包,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布桃序,位于F島的核電站,受9級特大地震影響烂瘫,放射性物質(zhì)發(fā)生泄漏媒熊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一坟比、第九天 我趴在偏房一處隱蔽的房頂上張望芦鳍。 院中可真熱鬧,春花似錦葛账、人聲如沸柠衅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菲宴。三九已至,卻和暖如春巩割,著一層夾襖步出監(jiān)牢的瞬間裙顽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工宣谈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留愈犹,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓闻丑,卻偏偏與公主長得像漩怎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嗦嗡,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354