Jprofiler簡介
-
Jprofilers是針對Java開發(fā)的
性能分析工具(免費試用10天)
, 可以對Java程序的內(nèi)存,CPU,線程,GC,鎖
等進行監(jiān)控和分析,
1.安裝及IDEA集成Jprofiler
本人IDEA版本是2020.2.2
,選擇的Jprofiler版本是12.0
(早期的版本是純英文的,12.0支持中文
,安裝主要考慮是否與IDEA插件兼容即可)
-
進入Jprofiler官網(wǎng)下載 -> Jprofiler 版本這邊建議選擇
最新的或者次新的
(前提是你的IEDA版本也比較新) -
打開IDEA->File->settings->plugins->marketplace->搜索 Jprofiler
-
插件安裝后, 在IDEA工具欄找到如圖所示的兩個圖標(biāo),點擊箭頭指的那個,進行
路徑配置
,讓插件能找到第1步中下載好的Jprofiler:-
選中后然后同意安裝協(xié)議,
一路next確認(rèn)就好了
,最后選試用10天激活
,如果你有秘鑰可以選永久激活,至此就安裝好了,然后可以通過該按鈕啟動應(yīng)用
-
啟動后就可以獲取到啟動應(yīng)用的各種信息了, 也可以
連接遠(yuǎn)程服務(wù)器上的java應(yīng)用,或者通過dump出來的文件分析等
:
-
2.如何監(jiān)控并解決死鎖
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(()->{
synchronized (a){
try {
System.out.println("已獲取到a鎖,嘗試獲取b鎖===>");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println("嘗試獲取b鎖...");
}
}
}).start();
new Thread(()->{
synchronized (b){
try {
System.out.println("<===已獲取到b鎖,嘗試獲取a鎖");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
System.out.println("嘗試獲取a鎖...");
}
}
}).start();
}
通過Jprofiler啟動這段代碼,然后可以在鎖菜單界面
直接看到當(dāng)前鎖的狀態(tài)圖:
然后可以在當(dāng)前Monitor監(jiān)視器
設(shè)置一個觀察閾值, 如果項目中的鎖比較多, 可以把超過一定時間的鎖給篩選出來
, 進一步觀察是否死鎖, 因為有些長時間阻塞的鎖`也可能是 餓鎖
然后針對篩選出來的幾個可能是死鎖的進行深入分析
, 首先可以看到這些鎖的類型是處于阻塞的,
然后鼠標(biāo)右鍵
任意一個鎖上面 選擇在堆遍歷器中顯式所選內(nèi)容:
然后在對堆遍歷器
中點擊圖表, 就可以看到這個鎖所在的代碼位置
, 然后 看一看代碼
就知道到底有沒有死鎖,還是餓鎖了
通常來說產(chǎn)生死鎖的原因主要有:
-
沒有設(shè)置鎖的失效時間
, 然后代碼里可能因為異常沒有處理, 鎖沒有在finaly代碼塊中釋放導(dǎo)致其它線程無法獲取鎖而陷入等待 -
鎖互相競爭(類似上圖這種)
,線程1持有A鎖嘗試獲取B鎖, 線程2持有B鎖嘗試獲取A鎖, 誰也不讓誰, 互相干等著。 這種情況要注意加鎖的順序
, 讓線程1是先獲得了A鎖,再去獲取B鎖, 然后線程2去嘗試獲取A鎖,再獲取B鎖, 按這樣的順序就不會產(chǎn)生死鎖. -
由線程優(yōu)先級產(chǎn)生的餓鎖
,即高優(yōu)先級線程一直占著資源不放而導(dǎo)致其他線程得不到執(zhí)行, 餓鎖不算死鎖,但也要引起重視,否則同樣會造成資源浪費, 可以通過JUC提供的公平鎖解決.
——>按照上面的思路,去排查對應(yīng)的代碼,基本上死鎖都能被解決.
3.如何監(jiān)控及解決內(nèi)存泄露(重點)
內(nèi)存泄漏相對于死鎖和OOM會比較難定位,而且對整個應(yīng)用的危害程度比較大, 一旦發(fā)生內(nèi)存泄漏,可能會導(dǎo)致整個應(yīng)用不可用.
-
內(nèi)存泄漏可以通過JDK自帶的
jconsole
或者jvisovm
都可以監(jiān)控到,這里演示下通過Jprofiler
如何監(jiān)控內(nèi)存泄漏. -
通常情況下,內(nèi)存泄漏會拖垮整個應(yīng)用,如果應(yīng)用突然不可用了, 且網(wǎng)絡(luò)正常. 可以服務(wù)器查看應(yīng)用日志,
如果看到有OOM,可以初步懷疑有內(nèi)存泄漏發(fā)生, 對于比較明顯 或者 比較短的時間內(nèi)產(chǎn)生的內(nèi)存泄漏,
可以通過本地IDEA直連Jprofile啟動應(yīng)用的方式進行監(jiān)控
,對于在特定場景下才會發(fā)生的,可以加上VM參數(shù):-XXHeapdump重啟線上應(yīng)用
, 以便在下次內(nèi)存泄漏OOM時,導(dǎo)出dump日志
,然后導(dǎo)入Jprofile進行分析
. 這里我以直連本地的方式進行演示(偷個懶)
public class Test {
private final static ThreadLocal<List<String>> threadLocal = new ThreadLocal<>();
private final static List<String> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(()->{
while (true){
for (int i = 0; i < 1000000; i++) {
list.add("oom");
if (i%100==0){
threadLocal.set(list);
}
}
//讓過程慢一點,留點時間去分析,否則OOM之后與Jprofiler的連接就斷開了
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
為了盡快的看到內(nèi)存泄漏又能不中斷與jprofiler的連接指定最大堆為1024M(-Xmx1024m
) , 以保證上面程序運行久一點, 不因為OOM中斷影響分析
(上圖是程序運行了很久之后)
- 用IDEA的Jprofiler插件啟動Jprofiler, 切換到
遙測->內(nèi)存界面
, 內(nèi)存使用量每增長一段時間后, 手動點擊一次運行GC, 觀察每次GC后已使用內(nèi)存部分(下圖中藍色部分
), 發(fā)現(xiàn)每次GC之后,已使用的內(nèi)存變得越來越多
, 如此積累下去,直到已使用內(nèi)存超過最大堆大小
,應(yīng)用就無法再創(chuàng)建對象, 然后瘋狂觸發(fā)GC, 但GC后又沒有效果, 最終GC過載
, CPU和內(nèi)存資源被榨干,服務(wù)處于不可用狀態(tài).一句話: 觀察
前一次GC 和 當(dāng)前GC后
剩余已用內(nèi)存
是否處于不斷遞增
之中,如果是 ,則極有可能發(fā)生了內(nèi)存泄漏.
通過線上服務(wù)不可用
和 監(jiān)控到的數(shù)據(jù)
, 幾乎可以確認(rèn)是發(fā)生了內(nèi)存泄漏,下面開始排查,到底是哪里發(fā)生了內(nèi)存泄漏?寫了什么禽獸代碼讓內(nèi)存泄漏了???
-
第一步:通過
Jprofiler連接到本地或者遠(yuǎn)程JVM
(考慮到安全問題,可以用遠(yuǎn)程dump的日志導(dǎo)入Jprofiler
) -
第二步:點擊
實時內(nèi)存菜單下的所有對象
, 然后點擊標(biāo)記當(dāng)前對象, 然后持續(xù)觀察實例計數(shù)列,相差列,大小列變化
可以從以下幾個線索去縮小范圍:
-
可能發(fā)生內(nèi)存泄漏的一般有 char[],String,Map,List,Object[] 等數(shù)據(jù),可以重點去看看
-
占據(jù)大量內(nèi)存空間 或是 實例計數(shù)超大的
一般會導(dǎo)致內(nèi)存泄漏且影響到服務(wù)正常吞吐的,
大小列的值都不會太小
,畢竟一個應(yīng)用的最大堆至少也會指定/默認(rèn)在256Mb以上
,像這種幾百kb級別的
,即便在持續(xù)增長,想要讓系統(tǒng)不可用
,也得積累很久很久 -
多次點擊運行GC,觀察每次GC后剩余的大小,
如果大小列的值增長很快,但GC后仍剩余很大
,可以懷疑此處可能有內(nèi)存泄漏比如下圖被我框了的地方,
GC前有400多Mb
,GC后仍有186MB剩余
,整個堆區(qū)在GC后剩余的總垃圾也不過189MB
, 這玩意就獨占鰲頭186Mb
,回收不掉,隔一陣子再點GC,GC后這個值比186MB還大
,再次印證該類很可能存在內(nèi)存泄漏)通過上面3種排查方式,逐步縮小排查范圍,目前看下來就只有
char[],String,Object[]三種類型的數(shù)據(jù)可能存在內(nèi)存泄露
-
第三步: 選中可疑項,點擊
鼠標(biāo)右鍵
,選擇在堆遍歷器中顯式
在打開的新界面中,點擊最大對象
, 然后選中保留大小最大的
(有的時候有好幾個都挺大的,只能一一嘗試,因為保留大小偏大的一般都可能導(dǎo)致內(nèi)存泄漏),然后點擊鼠標(biāo)右鍵,
使用選中對象
然后選擇傳入引用
,點擊確定
在下圖中的界面,可以先展開樹形結(jié)構(gòu)的數(shù)據(jù)看一下,這個Oject[]底下是什么東西
, 一般來說如果點開是大公司打頭的包名,基本可以直接跳過不用看了,因為一般大公司的產(chǎn)品不大會出現(xiàn)內(nèi)存泄漏,發(fā)布之前他們應(yīng)該也有測過了.
- 如果看了下包名,是
自己應(yīng)用的包名,
就可以繼續(xù)排查,點擊在圖表中顯示
在彈出的新頁面中選中上一步中看到的ArrayList對象
,點擊顯示到GC根的路徑:
然后可以點開+號
,瞅瞅, 基本上到這一步,就能確認(rèn)到發(fā)生內(nèi)存泄漏的代碼所在的包名+類名了
, 然后重點去排查這段代碼即可
原因 :
- 因為
ThreadLocal持有List<String>,
然后ThreadLocal并沒有remove()
,導(dǎo)致List<String>被強引用
,無法被GC,致使內(nèi)存泄漏.
4.總結(jié)
-
如果沒有Jprofiler這類工具, 在生產(chǎn)環(huán)境發(fā)生內(nèi)存泄漏, 去一行一行review所有代碼是不現(xiàn)實的, 通過此工具我們可以在較短的時間內(nèi)定位到導(dǎo)致內(nèi)存泄漏出現(xiàn)的代碼位置, 然后review該位置的代碼即可.
-
內(nèi)存泄漏的難的主要是
定位
, 解決起來一般比較簡單, 重啟生產(chǎn)環(huán)境應(yīng)用,讓服務(wù)恢復(fù)正常, 然后在把導(dǎo)致內(nèi)存泄漏的代碼優(yōu)化(該釋放的釋放,強引用顯示置為為null...
),改好之后測試并重新發(fā)布即可解決.文章來源:http://www.zghlxwxcb.cn/news/detail-825514.html
5.后話
當(dāng)這些問題出現(xiàn)在生產(chǎn)環(huán)境時,一般都會帶來嚴(yán)重的損失,而且排查和分析起來難度非常大, 在養(yǎng)成良好編碼習(xí)慣, 盡量去規(guī)避死鎖,內(nèi)存溢出,及內(nèi)存泄漏, 防患于未然是最好的
。文章來源地址http://www.zghlxwxcb.cn/news/detail-825514.html
- 多學(xué)點監(jiān)控和解決方式, 或許有一天還真能派上用場.像這類
低頻使用
的知識,最好能總結(jié)一遍,留下文檔,
下次再碰到時,可以快速回憶,
到了這里,關(guān)于【Jvm】性能調(diào)優(yōu)(拓展)Jprofiler如何監(jiān)控和解決死鎖、內(nèi)存泄露問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!