① 如何分析jvm mp 內存日誌
當伺服器掛起,崩潰或者性能底下時,就需要抓取伺服器的線程堆棧(Thread Dump)用於後續的分析.
Thread mp提供了當前活動的線程的快照. 它提供了JVM中所有java線程的棧跟蹤信息
有很多方式可用於獲取Thread Dump, 一些是操作系統特定的命令.
操作系統命令獲取ThreadDump:
Windows:
1. 轉向伺服器的標准輸出窗口並按下Control + Break組合鍵, 之後需要將線程堆棧復制到文件中
UNIX/ Linux
首先查找到伺服器的進程號(process id), 然後獲取堆棧.
1. ps –ef | grep java
2. kill -3
注意一定要謹慎, 一步不慎就可能讓伺服器進程被殺死!
JVM 自帶的工具獲取線程堆棧:
JDK自帶命令行工具獲取PID並做ThreadDump:
1. jps
2. jstack
使用JVisualVM:
Threads 標簽頁àThreadDump按鈕.
WebLogic 自帶的獲取 thread mp的工具:
1. webLogic.Admin 工具
a. 打開命令提示符, 通過運行/bin/setDomain.env設置相關類路徑
b. 執行下面的命令
java weblogic.Admin -url t3://localhost:7001 -username weblogic -password weblogic1 THREAD_DUMP
注意: Thread Dump 會列印到標准輸出, 如nohup日誌或者進程窗口.
2. 使用 Admin Console
a. 登錄 Admin Console , 點擊對應的伺服器
b. 點擊Server à Monitoring àThreads
c. 點擊: Dump Thread Stack 按鈕
3. 使用WLST (WebLogic Scripting Tool)
connect(『weblogic』,'weblogic1』,』t3://localhost:7001』)
cd(『Servers』)
cd(『AdminServer』)
threadDump()
disconnect()
exit()
注意: 線程堆棧將會保存在運行wlst的當前目錄下.
4. 使用utils.ThreadDumper
用法:
C:\bea\wlserver_10.3\server\lib>java -cp weblogic.jar utils.ThreadDumper
Broadcast Thread mps disabled: must specify weblogic.debug.mpThreadAddr and
weblogic.debug.mpThreadPort
Exception in thread "main" java.lang.I llegalArgumentException: Port out of range
:-1
at java.net.DatagramPacket.setPort(Unknown Source)
at java.net.DatagramPacket.(Unknown Source)
at java.net.DatagramPacket.(Unknown Source)
at utils.ThreadDumper.sendDumpMsg(ThreadDumper.java:124)
at utils.ThreadDumper.main(ThreadDumper.java:145)
5. 如果伺服器是作為Windows服務的方式運行, 請運行下列命令:
WL_HOME\bin\beasvc -mp -svcname:service-name
② 如何分析java Thread DUMP
一、Thread Dump介紹
1.1什麼是Thread Dump?
Thread Dump是非常有用的診斷Java應用問題的工具。每一個Java虛擬機都有及時生成所有線程在某一點狀態的thread-mp的能力,雖然各個 Java虛擬機列印的thread mp略有不同,但是大多都提供了當前活動線程的快照,及JVM中所有Java線程的堆棧跟蹤信息,堆棧信息一般包含完整的類名及所執行的方法,如果可能的話還有源代碼的行數。
1.2 Thread Dump特點
1. 能在各種操作系統下使用
2. 能在各種Java應用伺服器下使用
3. 可以在生產環境下使用而不影響系統的性能
4. 可以將問題直接定位到應用程序的代碼行上
1.3 Thread Dump 能診斷的問題
1. 查找內存泄露,常見的是程序里load大量的數據到緩存;
2. 發現死鎖線程;
1.4如何抓取Thread Dump
一般當伺服器掛起,崩潰或者性能底下時,就需要抓取伺服器的線程堆棧(Thread Dump)用於後續的分析. 在實際運行中,往往一次 mp的信息,還不足以確認問題。為了反映線程狀態的動態變化,需要接連多次做threadmp,每次間隔10-20s,建議至少產生三次 mp信息,如果每次 mp都指向同一個問題,我們才確定問題的典型性。
有很多方式可用於獲取ThreadDump, 下面列出一部分獲取方式:
操作系統命令獲取ThreadDump:
Windows:
1.轉向伺服器的標准輸出窗口並按下Control + Break組合鍵, 之後需要將線程堆棧復制到文件中;
UNIX/ Linux:
首先查找到伺服器的進程號(process id), 然後獲取線程堆棧.
1. ps –ef | grep java
2. kill -3 <pid>
注意:一定要謹慎, 一步不慎就可能讓伺服器進程被殺死。kill -9 命令會殺死進程。
JVM 自帶的工具獲取線程堆棧:
JDK自帶命令行工具獲取PID,再獲取ThreadDump:
1. jps 或 ps –ef|grepjava (獲取PID)
2. jstack [-l ]<pid> | tee -a jstack.log (獲取ThreadDump)
二、java線程的狀態轉換介紹(為後續分析做准備)
2.1 新建狀態(New)
用new語句創建的線程處於新建狀態,此時它和其他Java對象一樣,僅僅在堆區中被分配了內存。
2.2 就緒狀態(Runnable)
當一個線程對象創建後,其他線程調用它的start()方法,該線程就進入就緒狀態,Java虛擬機會為它創建方法調用棧和程序計數器。處於這個狀態的線程位於可運行池中,等待獲得CPU的使用權。
2.3 運行狀態(Running)
處於這個狀態的線程佔用CPU,執行程序代碼。只有處於就緒狀態的線程才有機會轉到運行狀態。
2.4 阻塞狀態(Blocked)
阻塞狀態是指線程因為某些原因放棄CPU,暫時停止運行。當線程處於阻塞狀態時,Java虛擬機不會給線程分配CPU。直到線程重新進入就緒狀態,它才有機會轉到運行狀態。
阻塞狀態可分為以下3種:
1)位於對象等待池中的阻塞狀態(Blocked in object』s wait pool):當線程處於運行狀態時,如果執行了某個對象的wait()方法,Java虛擬機就會把線程放到這個對象的等待池中,這涉及到「線程通信」的內容。
2)位於對象鎖池中的阻塞狀態(Blocked in object』s lock pool):當線程處於運行狀態時,試圖獲得某個對象的同步鎖時,如果該對象的同步鎖已經被其他線程佔用,Java虛擬機就會把這個線程放到這個對象的鎖池中,這涉及到「線程同步」的內容。
③ 在新建虛擬機時出現問題
JConsole
JConsole 圖形用戶界面是一種符合 Java 管理擴展(JMX)規范的監視工具。JConsole 使用 Java 虛擬機 (Java VM) 的廣泛檢測來提供有關在 Java 平台上運行的應用程序的性能和資源消耗的信息。
使用方法 本地
使用jconsole命令:監視本地運行的所有 Java 應用程序,JConsole 可以連接到這些應用程序。
使用jconsole PID命令:監視指定PID的Java應用程序。
使用jsconsole hostName:portNum命令:hostName是運行應用程序的系統的名稱,portNum是您在啟動Java VM時啟用 JMX 代理時指定的埠號。
使用service:jmx::命令:使用 JMX 服務 URL 進行連接。
內容分析
將 JConsole 連接到應用程序後,JConsole 由六個選項卡組成。
概述:顯示有關 Java VM 和受監視值的概述信息。
內存:顯示有關內存使用的信息。
線程:顯示有關線程使用的信息。
類:顯示有關類載入的信息。
VM:顯示有關 Java VM 的信息。
MBeans:顯示有關 MBeans 的信息。
顯示有關 CPU 使用情況、內存使用情況、線程計數和在Java VM中載入的類的圖形監視信息。
提供執行GC的操作,可以隨時點擊按鈕進行垃圾回收
伊甸園空間(堆):最初為大多數對象分配內存的池。
倖存者空間(堆):包含在伊甸園空間垃圾回收中倖存下來的物體的池。
終身代(堆):包含在倖存者空間中存在一段時間的對象的池。
永久生成(非堆):包含虛擬機本身的所有反射數據的池,如類和方法對象。使用類數據共享的 Java VM,這一代分為只讀和讀寫區域。
代碼緩存(非堆):HotSpotJava VM 還包括一個代碼緩存,其中包含用於編譯和存儲本機代碼的內存。
Java VM管理兩種類型的內存:堆內存和非堆內存,這兩種內存都是在 Java VM 啟動時創建的。
堆內存是Java VM為所有類實例和數組分配內存的運行時數據區域。堆的大小可能是固定的或可變的。垃圾回收器是一個自動內存管理系統,用於回收對象的堆內存。
非堆內存包括所有線程之間共享的方法區域和Java VM的內部處理或優化所需的內存。它存儲每類結構,如運行時常量池、欄位和方法數據,以及方法和構造函數的代碼。方法區域在邏輯上是堆的一部分,但是,根據實現,Java VM 可能不會對它進行垃圾回收或壓縮。與堆內存一樣,方法區域可能為固定大小或可變大小。方法區域的內存不需要連續。
內存池和內存管理器是Java VM內存系統的關鍵方面。
內存池表示Java VM管理的內存區域。Java VM至少有一個內存池,它可能會在執行期間創建或刪除內存池。內存池可以屬於堆內存或非堆內存。
內存管理器管理一個或多個內存池。垃圾回收器是一種內存管理器,負責回收不可到達的對象使用的內存。Java VM可能具有一個或多個內存管理器。它可以在執行期間添加或刪除內存管理器。內存池可以由多個內存管理器管理。
垃圾回收 (GC) 是Java VM釋放不再引用的對象佔用的內存的方式。通常認為具有活動引用為"活動"且未引用(或無法訪問)對象的對象為"已死"。垃圾回收是釋放死對象使用的內存的過程。GC 使用的演算法和參數對性能有顯著影響。
Java hotspot VM垃圾回收器使用代數 GC。生成 GC 利用大多數程序符合以下概括的觀察。
它們創建許多壽命較短的對象,例如迭代器和局部變數。
它們創建一些壽命很長的對象,例如高級持久對象。
提供有關線程使用的信息。
查找監視器死鎖線程:檢測對象監視器鎖上是否有任何線程死鎖。此操作返回死鎖線程指示的數組。
getThreadInfo:返回線程信息。這包括線程當前被阻止的名稱、堆棧跟蹤和監視器鎖(如果有)以及持有該鎖的線程以及線程爭用統計信息。
獲取ThreadCpu時間:返回給定線程消耗的 CPU 時間
顯示有關類載入的信息。
提供有關Java VM的信息。
以通用方式顯示有關在平台 MBean 伺服器注冊的所有 MBeans 的信息。MBeans 選項卡允許您訪問平台 MXBean 檢測的完整集,包括在其他選項卡中不可見的儀器。此外,您還可以使用 MBeans 選項卡監視和管理應用程序的 MBeans。
列出目標系統上已檢測的 Java 虛擬機 (JVM)。
監視 Java 虛擬機 (JVM) 統計信息。
對Java應用程序的資源和性能進行實時的命令行的監控,包括了對Heap size和垃圾回收狀況的監控。
命令格式
jstat [-option] [PID]
option參數
class:顯示有關類載入器行為的統計信息。
compiler:顯示有關Java HotSpot VM實時編譯器行為的統計信息。
gc:顯示有關垃圾回收堆行為的統計信息。
gccapacity:顯示有關幾代人容量及其相應空間的統計信息。
gccause:顯示有關垃圾回收統計信息(與 相同)的摘要,以及最後和當前(如果適用)垃圾回收事件的原因。-gcutil
gcnew:顯示新一代行為的統計信息。
gcnewcapacity:顯示有關新一代大小及其相應空間的統計信息。
gcold:顯示有關舊一代和元空間統計信息行為的統計信息。
gcoldcapacity:顯示有關舊一代大小的統計信息。
gcmetacapacity:顯示有關元空間大小的統計信息。
gcutil:顯示有關垃圾回收統計信息的摘要。
printcompilation:顯示 Java 熱點 VM 編譯方法統計信息。
1.jstat –class: 顯示載入class的數量,及所佔空間等信息。
2.jstat -compiler顯示VM實時編譯的數量等信息。
3.jstat -gc: 可以顯示gc的信息,查看gc的次數,及時間。
4.jstat -gccapacity:可以顯示,VM內存中三代(young,old,perm)對象的使用和佔用大小
5.jstat -gcutil:統計gc信息
6.jstat -gcnew:年輕代對象的信息。
7.jstat -gcnewcapacity: 年輕代對象的信息及其佔用量。
8.jstat -gcold:old代對象的信息。
9.jstat -gcoldcapacity: old代對象的信息及其佔用量。
10.jstat -gcpermcapacity: perm對象的信息及其佔用量。
11.jstat -printcompilation:當前VM執行的信息。
監視 Java 虛擬機 (JVM),並使遠程監視工具能夠連接到 JVM
命令格式
jstatd -[option]
option
-nr當找不到現有的RMI注冊表時,不嘗試使用jstatd進程創建一個內部的RMI注冊表。
-p port在指定的埠查找RMI注冊表。如果沒有找到,並且沒有指定-nr選項,則在該埠自行創建一個內部的RMI注冊表。
-n rminameRMI注冊表中綁定的RMI遠程對象的名稱。默認的名稱為JStatRemoteHost。如果多個jstatd伺服器在同一主機上運行,你可以通過指定該選項來讓每個伺服器導出的RMI對象具有唯一的名稱。不管如何,這樣做需要將唯一的伺服器名稱包含進監控客戶端的hostid和vmid字元串中。
-Joption將選項參數傳遞給被javac調用的java啟動程序。例如,-J-Xms48m設置啟動內存為48 MB。使用-J將選項參數傳遞給執行Java應用程序的底層虛擬機,這是一種常見慣例。
使用方法
1.在jdk的bin目錄下創建文件jstatd.all.policy
2.寫入下面的安全配置
grant codebase "file:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64/lib/tools.jar" {
permission java.security.AllPermission;
#此處寫絕對路徑,主要是防止路徑錯誤問題,排查問題,應該寫成相對路徑
3.啟動jstatd
./jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=x.x.x.x &
4.使用jvisualvm工具遠程連接,進行監控
jvisualvm
VisualVM,能夠監控線程,內存情況,查看方法的CPU時間和內存中的對 象,已被GC的對象,反向查看分配的堆棧(如100個String對象分別由哪幾個對象分配出來的).
同時他還提供很多插件可以自己安裝,是一款不錯的監控分析工具。
故障排除工具 JInfo
可以用來查看正在運行的 java 應用程序的擴展參數,包括Java System屬性和JVM命令行參數;也可以動態的修改正在運行的 JVM 一些參數。當系統崩潰時,jinfo可以從core文件裡面知道崩潰的Java應用程序的配置信息
命令格式
參數說明
pid對應jvm的進程id
executable core產生core mp文件
[server-id@]remote server IP or hostname遠程的ip或者hostname,server-id標記服務的唯一性id
option
no option輸出全部的參數和系統屬性
-flag name輸出對應名稱的參數
-flag [+|-]name開啟或者關閉對應名稱的參數
-flag name=value設定對應名稱的參數
-flags輸出全部的參數
-sysprops輸出系統屬性
Javacore 概述
Javacore,也可以稱為「threadmp」或是「javamp」,它是 Java 提供的一種診斷特性,能夠提供一份可讀的當前運行的 JVM 中線程使用情況的快照。即在某個特定時刻,JVM 中有哪些線程在運行,每個線程執行到哪一個類,哪一個方法。應用程序如果出現不可恢復的錯誤或是內存泄露,就會自動觸發 Javacore 的生成。
使用方法
1.jinfo pid:輸出當前 jvm 進程的全部參數和系統屬性
2.jinfo -flag name pid:輸出對應名稱的參數使用該命令,可以查看指定的 jvm 參數的值。如:查看當前 jvm 進程是否開啟列印 GC 日誌。
3.jinfo -flag [+|-]name pid:開啟或者關閉對應名稱的參數
使用 jinfo 可以在不重啟虛擬機的情況下,可以動態的修改 jvm 的參數。尤其在線上的環境特別有用。
4.jinfo -flag name=value pid:修改指定參數的值。
5.jinfo -flags pid:輸出全部的參數
6.jinfo -sysprops pid:輸出當前 jvm 進行的全部的系統屬性
jhat
主要是用來分析java堆的命令,可以將堆中的對象以html的形式顯示出來,包括對象的數量,大小等等,並支持對象查詢語言。
1.使用jmap命令導出堆文件jmap -mp:live,file=a.log pid
也可以使用下面方式導出堆文件
1、使用jconsole選項通過HotSpotDiagnosticMXBean從運行時獲得堆轉儲(生成mp文件)、
2、虛擬機啟動時如果指定了-XX:+HeapDumpOnOutOfMemoryError選項, 則在拋出OutOfMemoryError時, 會自動執行堆轉儲。
3、使用hprof命令
2.使用jhat分析堆文件jhat -J-Xmx512M a1.log
3.查看分析的html頁面
http://ip:7000/jhat中的OQL(對象查詢語言)
如果需要根據某些條件來過濾或查詢堆的對象,這是可能的,可以在jhat的html頁面中執行OQL,來查詢符合條件的對象
基本語法:
select
[from [instanceof] ]
[where ]
解釋:
(1)class name是java類的完全限定名,如:java.lang.String,java.util.ArrayList, C是char數組,java.io.File是java.io.File[]
(2)類的完全限定名不足以唯一的辨識一個類,因為不同的ClassLoader載入的相同的類,它們在jvm中是不同類型的
(3)instanceof表示也查詢某一個類的子類,如果不明確instanceof,則只精確查詢class name指定的類
(4)from和where子句都是可選的
(5)java域表示:obj.field_name;java數組表示:array[index]
舉例:
(1)查詢長度大於100的字元串
select s from java.lang.String s where s.count > 100
(2)查詢長度大於256的數組
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 -- 堆根集的枚舉
辨識對象的函數
(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 "+ toHtml(o) + "" from java.lang.Object o
(11)選擇多值
select {name:t.name?t.name.toString():"null",thread:t} from instanceof java.lang.Thread t
數組、迭代器等函數
(1)concat(enumeration1,enumeration2) -- 將數組或枚舉進行連接
select concat(referrers(p),referrers(p)) from java.util.Properties p
(2)contains(array, expression) -- 數組中元素是否滿足某表達式
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 -- 被迭代的數組
(3)count(array, expression) -- 滿足某一條件的元素的數量
select count(heap.classes(), "/java.io./(it.name)")
(4)filter(array, expression) -- 過濾出滿足某一條件的元素
select filter(heap.classes(), "/java.io./(it.name)")
(5)length(array) -- 返回數組長度
select length(heap.classes())
(6)map(array,expression) -- 根據表達式對數組中的元素進行轉換映射
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) -- 返回數組
(11)unique(array) -- 唯一化數組
jmap
列印進程、核心文件或遠程調試伺服器的共享對象內存映射或堆內存詳細信息。
jmap [option]
(to connect to running process) 連接到正在運行的進程
jmap [option]
(to connect to a core file) 連接到核心文件
jmap [option] [server_id@]
(to connect to remote debug server) 連接到遠程調試服務
option
pid:目標進程的PID,進程編號,可以採用ps -ef | grep java查看java進程的PID;
executable:產生core mp的java可執行程序;
core:將被列印信息的core mp文件;
remote-hostname-or-IP:遠程debug服務的主機名或ip;
server-id:唯一id,假如一台主機上多個遠程debug服務;
使用方法
jmap -mp:[live,]format=b,file= PID:使用hprof二進制形式,輸出jvm的heap內容到文件
jmap -finalizerinfo PID:列印正等候回收的對象的信息
jmap -heap PID:列印heap的概要信息,GC使用的演算法,heap(堆)的配置及JVM堆內存的使用情況。
jmap -histo:live PID:列印每個class的實例數目,內存佔用,類全名信息。VM的內部類名字開頭會加上前綴」*」. 如果live子參數加上後,只統計活的對象數量.
jmap -permstat PID:列印classload和jvm heap長久層的信息. 包含每個classloader的名字、活潑性、地址、父classloader和載入的class數量。另外,內部String的數量和佔用內存數也會列印出來。
-F強迫.在pid沒有相應的時候使用-mp或者-histo參數。在這個模式下,live子參數無效。
-h | -help列印輔助信息
-J傳遞參數給jmap啟動的jvm.
jstack命令主要用於調試java程序運行過程中的線程堆棧信息,可以用於檢測死鎖,進程耗用cpu過高報警問題的排查。jstack命令會列印出所有的線程,包括用戶自己啟動的線程和jvm後台線程。
命令格式
jstack -[option] pid
option
-F強制mp線程堆棧信息. 用於進程hung住,jstack命令沒有響應的情況
-m同時列印java和本地(native)線程棧信息,m是mixed mode的簡寫
-l列印鎖的額外信
公眾號「Java精選」所發表內容註明來源的,版權歸原出處所有(無法查證版權的或者未註明出處的均來自網路,系轉載,轉載的目的在於傳遞更多信息,版權屬於原作者。如有侵權,請聯系,筆者會第一時間刪除處理!
最近有很多人問,有沒有讀者交流群!加入方式很簡單,公眾號Java精選,回復「加群」,即可入群!
(微信小程序):3000+道面試題,包含Java基礎、並發、JVM、線程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架構設計等,在線隨時刷題!
------ 特別推薦 ------
特別推薦:專注分享最前沿的技術與資訊,為彎道超車做好准備及各種開源項目與高效率軟體的公眾號,「大咖筆記」,專注挖掘好東西,非常值得大家關注。點擊下方公眾號卡片關注。
文章有幫助的話,在看,轉發吧!
④ 怎樣分析 JAVA 的 Thread Dumps
當有障礙,或者是一個基於 JAVA 的 WEB 應用運行的比預期慢的時候,我們需要使用 thread mps。如果對於你來說,thread mps 是非常復雜的,這篇文章或許能對你有所幫助。在這里我將解釋在 JAVA 中什麼是 threads,他們的類型,怎麼被創建的,怎樣管理它們,你怎樣從正在運行的應用中 mp threads,最後你可以怎樣分析它以及確定瓶頸或者是阻塞線程。本文來自於 JAVA 應用程序長期調試經驗的結果。
Java and Thread
一個 web 伺服器使用幾十到幾百個線程來處理大量並發用戶,如果一個或多個線程使用相同的資源,線程之間的競爭就不可避免了,並且有時候可能會發生死鎖。
Thread contention 是一個線程等待鎖的一個狀態,這個鎖被另外一個線程持有,等待被釋放,不同的線程頻繁訪問 WEB 應用的共享資源。例如,記錄一條日誌,線程嘗試記錄日誌之前必須先獲取鎖來訪問共享資源。
死鎖是線程競爭的一個特殊狀態,一個或是多個線程在等待其他線程完成它們的任務為了完成它們自己的任務。
線程競爭會引起各種不同的問題,為了分析這些這些問題,你需要使用 mp threads,mp threads 能給你提供每個線程的精確狀態信息。
JAVA 線程的背景資料
線程同步
一個線程可以與其他線程在同一時間內被處理。為了確保一致性,當多個線程試圖使用共享資源的時候,通過使用 hread synchronization 在同一時間內,應該只有一個線程能訪問共享資源
JAVA 中的線程同步可以使用監視器,每個 JAVA 對象都有一個單獨的監視器,這個監視器僅僅只能被一個線程擁有,對於擁有一個由不同的線程所擁有的監視器的線程,確實需要在隊列中等待,以便其他線程釋放它的監視器。
線程狀態
為了分析一個 thread mp 文件,你需要知道線程狀態。線程情況在 java.lang.Thread.State 中闡明了。
當使用 java.lang.Thread 對象創建線程的時候,線程被命名為 Thread-(Number) 。當使用 java.util.concurrent.DefaultThreadFactory 對象創建線程的時候,線程被命名為 named pool-(Number)-thread-(Number)。當為應用程序分析成百上千的線程的時候,如果線程依然用它們默認的名字,分析它們將變得非常困難,因為這是非常難以辨別這些線程來分析的。
因此,你被建議開發一個命名線程的規則當一個新線程被創建的時候。
當你使用 java.lang.Thread 創建線程,你可以通過創建參數給該線程定義個約定俗成的名字。
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name, long stackSize);
當你使用 java.util.concurrent.ThreadFactory 創建線程的時候,你可以通過生成你自己的線程工廠來命名它,如果你不需要特別的功能性,你可以使用 MyThreadFactory 作為以下描述:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThreadFactory implements ThreadFactory {
private static final ConcurrentHashMap<String, AtomicInteger> POOL_NUMBER =
new ConcurrentHashMap<String, AtomicInteger>();
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public MyThreadFactory(String threadPoolName) {
if (threadPoolName == null) {
throw new NullPointerException("threadPoolName");
}
POOL_NUMBER.putIfAbsent(threadPoolName, new AtomicInteger());
SecurityManager securityManager = System.getSecurityManager();
group = (securityManager != null) ? securityManager.getThreadGroup() :
Thread.currentThread().getThreadGroup();
AtomicInteger poolCount = POOL_NUMBER.get(threadPoolName);
if (poolCount == null) {
namePrefix = threadPoolName + " pool-00-thread-";
} else {
namePrefix = threadPoolName + " pool-" + poolCount.getAndIncrement() + "-thread-";
}
}
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
if (thread.isDaemon()) {
thread.setDaemon(false);
}
if (thread.getPriority() != Thread.NORM_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY);
}
return thread;
}
}
使用 MBean 獲取更多的細節信息
你可以使用 MBean 來獲取 ThreadInfo 對象。你也可以獲取更加多通過 thread mps 不能獲取的信息。通過使用 ThreadInfo。
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
long[] threadIds = mxBean.getAllThreadIds();
ThreadInfo[] threadInfos =
mxBean.getThreadInfo(threadIds);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(
threadInfo.getThreadName());
System.out.println(
threadInfo.getBlockedCount());
System.out.println(
threadInfo.getBlockedTime());
System.out.println(
threadInfo.getWaitedCount());
System.out.println(
threadInfo.getWaitedTime());
}
你可以使用方法 ThreadInfo 來提取阻塞線程或者是等待線程花費的時間。並利用這一點,你也可以得到那些處於非活動狀態的時間異常長的線程列表。
⑤ jvm 內存調優用過哪些工具,jstate 做什麼用的如何 mp 出當前線程狀態
實例一:Waiting to lock 和 Blocked
"RMI TCP Connection(267865)-172.16.5.25" daemon prio=10 tid=0x00007fd508371000 nid=0x55ae waiting for monitor entry [0x00007fd4f8684000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.log4j.Category.callAppenders(Category.java:201)
- waiting to lock <0x00000000acf4d0c0> (a org.apache.log4j.Logger)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.log(Category.java:853)
at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)
at com.tuan.core.common.lang.cache.remote.SpyMemcachedClient.get(SpyMemcachedClient.java:110)
說明:
1)線程狀態是 Blocked,阻塞狀態。說明線程等待資源超時!
2)「 waiting to lock <0x00000000acf4d0c0>」指,線程在等待給這個 0x00000000acf4d0c0 地址上鎖(英文可描述為:trying to obtain 0x00000000acf4d0c0 lock)。
3)在 mp 日誌里查找字元串 0x00000000acf4d0c0,發現有大量線程都在等待給這個地址上鎖。如果能在日誌里找到誰獲得了這個鎖(如locked < 0x00000000acf4d0c0 >),就可以順藤摸瓜了。
4)「waiting for monitor entry」說明此線程通過 synchronized(obj) {……} 申請進入了臨界區,從而進入了下圖1中的「Entry Set」隊列,但該 obj 對應的 monitor 被其他線程擁有,所以本線程在 Entry Set 隊列中等待。
5)第一行里,"RMI TCP Connection(267865)-172.16.5.25"是 Thread Name 。tid指Java Thread id。nid指native線程的id。prio是線程優先順序。[0x00007fd4f8684000]是線程棧起始地址。
實例二:Waiting on condition 和 TIMED_WAITING
"RMI TCP Connection(idle)" daemon prio=10 tid=0x00007fd50834e800 nid=0x56b2 waiting on condition [0x00007fd4f1a59000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:424)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:323)
at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:874)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:945)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
at java.lang.Thread.run(Thread.java:662)
說明:
1)「TIMED_WAITING (parking)」中的 timed_waiting 指等待狀態,但這里指定了時間,到達指定的時間後自動退出等待狀態;parking指線程處於掛起中。
2)「waiting on condition」需要與堆棧中的「parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)」結合來看。首先,本線程肯定是在等待某個條件的發生,來把自己喚醒。其次,SynchronousQueue 並不是一個隊列,只是線程之間移交信息的機制,當我們把一個元素放入到 SynchronousQueue 中時必須有另一個線程正在等待接受移交的任務,因此這就是本線程在等待的條件。
3)別的就看不出來了。
⑥ 如何分析java thread mp
thread mp解析
頭部信息
時間,jvm信息
{code}
2011-11-02 19:05:06
Full thread mp Java HotSpot(TM) Server VM (16.3-b01 mixed mode):
{code}
線程info信息塊
{code}
"Checkpointer" daemon prio=10 tid=0x68ce1c00 nid=0x7c11 in Object.wait() [0x68b5c000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x740ad988> (a java.lang.Object)
at java.lang.Object.wait(Object.java:485)
at com.sleepycat.je.utilint.DaemonThread.run(DaemonThread.java:163)
- locked <0x740ad988> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:619)
{code}
"Checkpointer" daemon prio=10 tid=0x68ce1c00 nid=0x7c11 in Object.wait() [0x68b5c000]
* 線程名稱:Checkpointer
* 線程類型:daemon
* 優先順序:10,默認是5
* jvm線程id:jvm內部線程的唯一標識,0x68ce1c00
* 對應系統線程id:和top命令查看的pid對應,不過一個是10進制,一個是16進制。0x7c11
* 線程狀態:Object.wait().
* 起始棧地址
線程狀態詳解
Runnable
_The thread is either running or ready to run when it gets its CPU turn._
不解釋。
Wait on condition
_The thread is either sleeping or waiting to be notified by another thread._
該狀態出現在線程等待某個條件的發生或者sleep。
_最常見的情況是線程在等待網路的讀寫,比如當網路數據沒有準備好讀時,線程處於這種等待狀態,而一旦有數據准備好讀之後,線程會重新激活,讀取並處理數據。_
Waiting for Monitor Entry and in Object.wait()
_The thread is waiting to get the lock for an object (some other thread may be holding the lock). This happens if two or more threads try to execute synchronized code. Note that the lock is always for an object and not for indivial methods._
當一個線程申請進入臨界區時,獲取到monitor,線程將處於 「Runnable」的狀態,否則,線程 DUMP會顯示處於 「waiting for monitor entry」。
當線程獲得了 Monitor,進入了臨界區之後,如果發現線程繼續運行的條件沒有滿足,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進入 「Wait Set」隊列。只有當別的線程在該對象上調用了 notify() 或者 notifyAll() , 「 Wait Set」隊列中線程才得到機會去競爭,但是只有一個線程獲得對象的 Monitor,恢復到運行態。在 「Wait Set」中的線程, DUMP中表現為: in Object.wait()。
例:
<span style="background-color: rgb(255, 255, 255);"><span style="color:#ff6666;">{code}
"Timer-0" daemon prio=10 tid=0x695c3000 nid=0x7c00 in Object.wait() [0x69468000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x744f2850> (a java.util.TaskQueue) ###繼續wait
at java.util.TimerThread.mainLoop(Timer.java:509)
- locked <0x744f2850> (a java.util.TaskQueue) ###已經lock到0x744f2850
at java.util.TimerThread.run(Timer.java:462)
{code}</span></span>
參見:http://jameswxx.iteye.com/blog/1041173
{code}
java.lang.Thread.State: WAITING (on object monitor)
<p style="margin-top: 4px; margin-right: 0px; margin-bottom: 4px; margin-left: 0px; padding-top: 2px; padding-right: 0px; padding-bottom: 2px; padding-left: 0px; ">{code}</p>
線程狀態運行:
WAITING||State || Description||
|blocked|This thread tried to enter a synchronized block, but the lock was taken by another thread. This thread is blocked until the lock gets released.|
|blocked (on thin lock)|This is the same state as blocked, but the lock in question is a thin lock.||waiting|This thread called Object.wait() on an object. The thread will remain there until some other thread sends a notification to that object.|
|sleeping|This thread called java.lang.Thread.sleep().||parked|This thread called java.util.concurrent.locks.LockSupport.park().||suspended|The thread's execution was suspended by java.lang.Thread.suspend() or a JVMTI agent call.|
{code}
at java.lang.Object.wait(Native Method)
- waiting on <0x740ad988> (a java.lang.Object) ###等待堆地址為0x740ad988的java.lang.Object對象的鎖
at java.lang.Object.wait(Object.java:485)
at com.sleepycat.je.utilint.DaemonThread.run(DaemonThread.java:163)
- locked <0x740ad988> (a java.lang.Object) ###hold住堆地址為0x740ad988的java.lang.Object對象的鎖
at java.lang.Thread.run(Thread.java:619)
{code}
⑦ Java 中怎麼獲取一份線程 mp 文件
當伺服器掛起,崩潰或者性能底下時,就需要抓取伺服器的線程堆棧(Thread Dump)用於後續的分析.
Thread mp提供了當前活動的線程的快照.它提供了JVM中所有Java線程的棧跟蹤信息
有很多方式可用於獲取Thread Dump,一些是操作系統特定的命令.
Windows:
1. 轉向伺服器的標准輸出窗口並按下Control + Break組合鍵,之後需要將線程堆棧復制到文件中
UNIX/ Linux
首先查找到伺服器的進程號(process id),然後獲取堆棧.
1. ps –ef| grep java
2. kill -3 <pid>
注意一定要謹慎,一步不慎就可能讓伺服器進程被殺死!
JVM自帶的工具獲取線程堆棧:
JDK自帶命令行工具獲取PID並做ThreadDump:
1. jps
2.jstack <pid>
使用JVisualVM:
Threads標簽頁 →ThreadDump按鈕
WebLogic自帶的獲取thread mp的工具:
1. webLogic.Admin工具
a.打開命令提示符,通過運行<DOMAIN_HOME>/bin/setDomain.env設置相關類路徑
b.執行下面的命令
java weblogic.Admin -url t3://localhost:7001 -username weblogic -password weblogic1 THREAD_DUMP
注意: Thread Dump會列印到標准輸出,如nohup日誌或者進程窗口.
2.使用 Admin Console
a.登錄Admin Console ,點擊對應的伺服器
b.點擊ServeràMonitoringàThreads
c.點擊: Dump Thread Stack按鈕
3.使用WLST (WebLogic Scripting Tool)
connect(『weblogic』,'weblogic1』,』t3://localhost:7001』)
cd(『Servers』)
cd(『AdminServer』)
threadDump()
disconnect()
exit()
注意:線程堆棧將會保存在運行wlst的當前目錄下.
4.使用utils.ThreadDumper
用法:
C:eawlserver_10.3serverlib>java -cp weblogic.jar utils.ThreadDumper
Broadcast Thread mps disabled: must specify weblogic.debug.mpThreadAddr and
weblogic.debug.mpThreadPort
Exception in thread "main" java.lang.IllegalArgumentException: Port out of range
:-1
at java.net.DatagramPacket.setPort(Unknown Source)
at java.net.DatagramPacket.<init>(Unknown Source)
at java.net.DatagramPacket.<init>(Unknown Source)
at utils.ThreadDumper.sendDumpMsg(ThreadDumper.java:124)
at utils.ThreadDumper.main(ThreadDumper.java:145)
5.如果伺服器是作為Windows服務的方式運行,請運行下列命令:
WL_HOMEineasvc -mp -svcname:service-name
$JAVA_
⑧ 如何抓取Thread Dump小結
當伺服器掛起,崩潰或者性能底下時,就需要抓取伺服器的線程堆棧(Thread Dump)用於後續的分析.
Thread mp提供了當前活動的線程的快照. 它提供了JVM中所有Java線程的棧跟蹤信息
有很多方式可用於獲取Thread Dump, 一些是操作系統特定的命令.
操作系統命令獲取ThreadDump:
Windows:
1. 轉向伺服器的標准輸出窗口並按下Control + Break組合鍵, 之後需要將線程堆棧復制到文件中
UNIX/ Linux
首先查找到伺服器的進程號(process id), 然後獲取堆棧.
1. ps –ef | grep java
2. kill -3 <pid>
注意一定要謹慎, 一步不慎就可能讓伺服器進程被殺死!
JVM 自帶的工具獲取線程堆棧:
JDK自帶命令行工具獲取PID並做ThreadDump:
1. jps
2. jstack <pid>
使用JVisualVM:
Threads 標簽頁àThreadDump按鈕.
WebLogic 自帶的獲取 thread mp的工具:
1. webLogic.Admin 工具
a. 打開命令提示符, 通過運行<DOMAIN_HOME>/bin/setDomain.env設置相關類路徑
b. 執行下面的命令
java weblogic.Admin -url t3://localhost:7001 -username weblogic -password weblogic1 THREAD_DUMP
注意: Thread Dump 會列印到標准輸出, 如nohup日誌或者進程窗口.
2. 使用 Admin Console
a. 登錄 Admin Console , 點擊對應的伺服器
b. 點擊Server à Monitoring àThreads
c. 點擊: Dump Thread Stack 按鈕
3. 使用WLST (WebLogic Scripting Tool)
connect(『weblogic』,'weblogic1』,』t3://localhost:7001』)
cd(『Servers』)
cd(『AdminServer』)
threadDump()
disconnect()
exit()
注意: 線程堆棧將會保存在運行wlst的當前目錄下.
4. 使用utils.ThreadDumper
用法:
C:\bea\wlserver_10.3\server\lib>java -cp weblogic.jar utils.ThreadDumper
Broadcast Thread mps disabled: must specify weblogic.debug.mpThreadAddr and
weblogic.debug.mpThreadPort
Exception in thread "main" java.lang.IllegalArgumentException: Port out of range
:-1
at java.net.DatagramPacket.setPort(Unknown Source)
at java.net.DatagramPacket.<init>(Unknown Source)
at java.net.DatagramPacket.<init>(Unknown Source)
at utils.ThreadDumper.sendDumpMsg(ThreadDumper.java:124)
at utils.ThreadDumper.main(ThreadDumper.java:145)
5. 如果伺服器是作為Windows服務的方式運行, 請運行下列命令:
WL_HOME\bin\beasvc -mp -svcname:service-name
其它一些獲取Thread Dump的工具有jrcmd, jrmc(JRockit VM自帶) ,Samurai, JProfiler等, 還可通過JMX編程的方式獲取, 如JDK自帶示例代碼:
$JAVA_HOME\demo\management\FullThreadDump