乾貨|JVM記憶體模型和常規問題定位手段
點選上方“中興開發者社群”,關注我們
每天讀一篇一線開發者原創好文
1.JVM是什麼
JVM是一種規範,它遮蔽了不同作業系統的差異,使得執行在其上的程式碼”一次編寫,到處執行”成為可能。通常所說的和使用的最多的是sun公司的一個JVM的實現版本(HotSpot),當然我們也可以自行實現JVM中的某些部件如“class loader”。JVM規範規定了JVM必須由如下部件組成:
(1)、 Class loader(類裝載器) 子系統;
根據給定的全限定名類名(如 java.lang.Object)來裝載class檔案的內容到 Runtime data area中的method area(方法區域)。Javsa程式設計師可以extends java.lang.ClassLoader類來寫自己的Class loader。
(2)、Execution engine(執行引擎) 子系統;
執行classes中的指令。任何JVM specification實現(JDK)的核心是Execution engine, 換句話說:Sun 的JDK 和IBM的JDK好壞主要取決於他們各自實現的Execution engine的好壞。每個執行中的執行緒都有一個Execution engine的例項。
(3)、Native interface(本地介面)元件;
Native interface與native libraries互動,是其它程式語言互動的介面。
(4)、Runtime data area (執行時資料區域)元件①
執行時資料區域元件包含:Heap (堆)、 Method Area(方法區域)、Java Stack(java的棧)、Program Counter(程式計數器)、Native method stack(本地方法棧);
接下來再來看下“堆”的內部細節。
2.堆記憶體模型
這兩個圖表達的意思差不多,堆記憶體由 年輕代和老年代組成,年輕代有分為Eden,S0(From),S1(To)
Eden: 伊甸園,萬物出生的地方, 程式執行時,所有被建立的物件都在這裡出生。
S0,S1: S表示Survivor倖存者, 這裡存放的是當Eden空間滿進過一次年輕代記憶體回收(YGC)所沒有被回收的物件,YGC通常採用“複製演算法”來進行垃圾回收,所以需要兩個Survivor空間來進行倒換。
Tenured(Old): 老年代, 物件不會直接進入老年代,老年代中的物件都是在GC的時候被JVM移入的。老年代滿的時候會觸發Full GC,Full GC通常採用“標記-整理演算法”來進行垃圾回收。這個過程會“stop-the-word”,需要掃描整個堆記憶體空間,所以也非常消耗系統資源。通常有以下幾種情況JVM會把物件移入老年代:
(1)、某個物件在經歷了多次YGC之後還倖存。
(2)、通常S0、S1都被設定得非常小,因為大多數的物件都是臨時變數,方法執行完之後就沒用了,只要少量物件會倖存。 當YGC發生時發現S0/S1存放不下倖存的物件的時候會將溢位的部分移入老年代。
3.問題定位思路和方法:
疑似記憶體洩露問題一般表現為兩種情況:(1). 程式還沒掛,但是cpu佔用非常高(通常200%+)。top命令檢視記憶體佔用也已經逼近最大值 (2).程式已經掛了,生成了dump檔案。
思路:
(1)、確認問題
第二種情況99%的情況都是記憶體耗盡導致的,直接分析dump檔案,對於第一種情況要首先確認是否是記憶體即將耗盡的表現。
對於第一種情況首先確認cpu衝高是否是FGC導致的,用jstat -gcutil pid 1000 10 命令檢視當前gc的情況②
如果FGC這一列的數值不斷增大,則說明程式在不斷的執行Full GC,那就說明記憶體即將耗盡。正常來說一次Full GC之後老年代O這一列的數值會降下去。
如果FGC這一列的數值不變,或者很久才增加一次,則說明程式的cpu衝高不是Full GC導致的,那麼就要考慮其他的情況了。有時候你會發現使用top命令看出記憶體佔用逼近最大值,但卻是Full GC執行的也不頻繁。這個時候就可以通過jstat -gc pid 1000 10命令來檢視更加詳細的記憶體佔用,通常都是OC(老年代總空間)比較大。 PS:linux的top命令檢視的是程式所佔用的記憶體,這裡可以理解為jvm從作業系統哪裡要來了很多記憶體,但是並沒有都利用起來,所以看起來記憶體佔用很高,但是Full GC並不頻繁。
RES 為程式佔用記憶體, 即為jvm佔用記憶體,基本等同於堆記憶體佔用和非堆記憶體佔用,它的最大值=Xmx + XX:MaxPermSize
如果不是Full GC導致的cpu衝高,那麼又是什麼原因導致的呢? 這時可以使用jstack pid 命令檢視當前程式中的所有執行緒都在幹啥。
(2)、定位問題
如果已經確認是記憶體耗盡瘋狂執行FullGC導致的問題,那接下來就要來定位為什麼記憶體一直被佔用著不能被釋放。
如果程式還在,可以快速的檢視堆中到底是什麼東西,用jmap -histo:live pid > a.txt 命令來檢視。ps:jmap -histo展示的是堆中所有的物件,包含垃圾和非垃圾,如果系統在不斷的執行FullGC,那麼就可以認為jmap輸出的都是非垃圾,jmap -histo:live和jmap -histo的區別是:在快照記憶體之前會進行一次FullGC.
根據所列的物件,聯絡自己的業務來定位問題。
如果程式已經掛了,或者根據jmap的輸出依然無法定位,那麼還可以使用第三方工具MAT來分析dump檔案。MAT本身也是基於jvm平臺開發的,所以執行它需要在你的電腦上裝好java。下載地址:http://www.eclipse.org/mat/downloads.php?
dump檔案有兩個手段獲取:
-
啟動jvm例項時增加引數:-XX:HeapDumpPath=xxxxxx,jvm程式在記憶體耗盡的時候會主動生成dump檔案
2.手動獲取dump檔案: 命令:jmap -dump:file=DumpFileName.hprof,format=b pid
MAT的介面:
通常分析Leak suspects頁籤就可以得到問題的答案。
點選detail檢視詳情:
這裡清晰的指出
akka.dispatch.Dispatcher$$anon$1 下的
akka.dispatch.UnboundedMailbox$MessageQueue 佔用了95.49的記憶體。
再接著分析這個物件被誰持有著,點選MessageQueue這一行,選擇List objects -》with incomming references。(with outgoing references可以檢視當前這個物件裡面有哪些屬性)
這裡展示引用MessageQueue的物件,同時也包含了除了MessageQueue之外的其他屬性。然後在最上層物件點選滑鼠右鍵選擇Path To GC Roots-》exclude all phantom/week/soft etc.references
然後逐層展開,去找你認識的物件型別:
找到了“相關”的物件,再根據程式碼分析為什麼會一直持有。
同時還可從Histogram頁簽入手分析:
這個列表和jmap命令輸出的內容非常相似,只不過jmap輸出的是全量的,而這個工具列出來的是無法被回收的。
這個例子中的問題正是由於我們使用akka時沒有限制郵箱長度,同時訊息由於其他問題無法被消費,訊息不斷的積壓造成的記憶體洩露。
(3)、效能調優
如果是在家裡做效能測試的時候想要實時觀測jvm例項的狀態來調優可以使用安裝java時自帶的VisualVM工具。(因為實時觀測遠端jvm例項需要開啟jvm的相關服務,所以生產環境用不起來。)
開發虛機上就有:執行/usr/local/java/bin/jvisualvm 即可開啟視覺化介面(檢測到我本機有7個jvm例項在執行):
VisualVM還可以新增外掛來滿足各種不同的需求場景。
一些參考資料:https://visualvm.java.net/zh_CN/gettingstarted.html
https://www.ibm.com/developerworks/cn/java/j-lo-visualvm/
註釋:
①:Runtime data area (執行時資料區域)元件:Heap (堆)、 Method Area(方法區域)、Java Stack(java的棧)、Program Counter(程式計數器)、Native method stack(本地方法棧)
Heap 和Method Area是被所有執行緒的共享使用的;而Java stack, Program counter 和Native method stack是以執行緒為粒度的,每個執行緒獨自擁有。
Heap Java程式在執行時建立的所有類實或陣列都放在同一個堆中。而一個Java虛擬例項中只存在一個堆空間,因此所有執行緒都將共享這個堆。每一個java程式獨佔一個JVM例項,因而每個java程式都有它自己的堆空間,它們不會彼此干擾。但是同一java程式的多個執行緒都共享著同一個堆空間,就得考慮多執行緒訪問物件(堆資料)的同步問題。 (這裡可能出現的異常java.lang.OutOfMemoryError: Java heap space)
Method Area 在Java虛擬機器中,被裝載的class的資訊儲存在Method area的記憶體中。當虛擬機器裝載某個型別時,它使用類裝載器定位相應的class檔案,然後讀入這個class檔案內容並把它傳輸到虛擬機器中。緊接著虛擬機器提取其中的型別資訊,並將這些資訊儲存到方法區。該型別中的類(靜態)變數同樣也儲存在方法區中。與Heap 一樣,method area是多執行緒共享的,因此要考慮多執行緒訪問的同步問題。比如,假設同時兩個執行緒都企圖訪問一個名為Lava的類,而這個類還沒有內裝載入虛擬機器,那麼,這時應該只有一個執行緒去裝載它,而另一個執行緒則只能等待。 (這裡可能出現的異常java.lang.OutOfMemoryError: PermGen full)
Java棧(Java Stack)是執行緒私有的,它的生命週期與執行緒相同。Java stack以幀為單位儲存執行緒的執行狀態。虛擬機器只會直接對Java stack執行兩種操作:以幀為單位的壓棧或出棧。每當執行緒呼叫一個方法的時候,就對當前狀態作為一個幀儲存到java stack中(壓棧);當一個方法呼叫返回時,從java stack彈出一個幀(出棧)。棧的大小是有一定的限制,這個可能出現StackOverFlow問題。
程式計數器(Program counter)是一塊較小的記憶體空間,它的作用可以看做是當前執行緒所在會想的位元組碼的行號指示器。位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令、分支、迴圈、跳轉、異常處理執行緒恢復等基礎功能都需要依賴這個計數器來完成。 每個執行中的Java程式,每一個執行緒都有它自己的PC暫存器,也是該執行緒啟動時建立的。PC暫存器的內容總是指向下一條將被執行指令的地址,這裡的地址可以是一個本地指標,也可以是在方法區中相對應於該方法起始指令的偏移量。
Native method stack 對於一個執行中的Java程式而言,它還能會用到一些跟本地方法相關的資料區。當某個執行緒呼叫一個本地方法時,它就進入了一個全新的並且不再受虛擬機器限制的世界。本地方法可以通過本地方法介面來訪問虛擬機器的執行時資料區,不止與此,它還可以做任何它想做的事情。比如,可以呼叫暫存器,或在作業系統中分配記憶體等。總之,本地方法具有和JVM相同的能力和許可權。 (這裡出現JVM無法控制的記憶體溢位問題native heap OutOfMemory );
②:輸出引數內容
S0 — Heap上的 Survivor space 0 區已使用空間的百分比
S0C:S0當前容量的大小
S0U:S0已經使用的大小
S1 — Heap上的 Survivor space 1 區已使用空間的百分比
S1C:S1當前容量的大小
S1U:S1已經使用的大小
E — Heap上的 Eden space 區已使用空間的百分比
EC:Eden space當前容量的大小
EU:Eden space已經使用的大小
O — Heap上的 Old space 區已使用空間的百分比
OC:Old space當前容量的大小
OU:Old space已經使用的大小
P — Perm space 區已使用空間的百分比
PC:Perm space當前容量的大小
PU:Perm space已經使用的大小
YGC — 從應用程式啟動到取樣時發生 Young GC 的次數
YGCT– 從應用程式啟動到取樣時 Young GC 所用的時間(單位秒)
FGC — 從應用程式啟動到取樣時發生 Full GC 的次數
FGCT– 從應用程式啟動到取樣時 Full GC 所用的時間(單位秒)
GCT — 從應用程式啟動到取樣時用於垃圾回收的總時間(單位秒),它的值等於YGC+FGC
參考資料:
垃圾回收演算法:http://www.cnblogs.com/AloneSword/p/4262255.html
jvm配置引數:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
相關文章
- Java記憶體模型常見問題Java記憶體模型
- JVM 記憶體模型 記憶體分配,JVM鎖JVM記憶體模型
- 記憶體和棧溢位問題定位記憶體
- JVM記憶體管理面試常見問題全解JVM記憶體面試
- JVM記憶體結構、Java記憶體模型和Java物件模型JVM記憶體Java模型物件
- JVM記憶體模型JVM記憶體模型
- 淺談JVM記憶體結構 和 Java記憶體模型 和 Java物件模型JVM記憶體Java模型物件
- JVM-記憶體模型JVM記憶體模型
- JVM記憶體模型(五)JVM記憶體模型
- JVM 常見線上問題 → CPU 100%、記憶體洩露 問題排查JVM記憶體洩露
- 【JVM】JVM系列之記憶體模型(六)JVM記憶體模型
- JVM堆外記憶體問題排查JVM記憶體
- JVM與記憶體洩露問題JVM記憶體洩露
- 再探JVM記憶體模型JVM記憶體模型
- JVM記憶體模型總結JVM記憶體模型
- JVM記憶體模型詳解JVM記憶體模型
- 深入理解JVM(一)——JVM記憶體模型JVM記憶體模型
- 深入理解JVM(一)JVM記憶體模型JVM記憶體模型
- 技術問答集錦(15)JVM記憶體模型JVM記憶體模型
- 九、JVM記憶體模型詳解JVM記憶體模型
- [效能]【JVM】關於JVM記憶體的N個問題JVM記憶體
- JVM執行緒和記憶體溢位問題排查思路JVM執行緒記憶體溢位
- 深入理解JVM(1)之--JVM記憶體模型JVM記憶體模型
- 深入理解JVM-記憶體模型(jmm)和GCJVM記憶體模型GC
- JVM面試問題系列:深入詳解JVM 記憶體區域及記憶體溢位分析JVM面試記憶體溢位
- Java記憶體模型FAQ(五)舊的記憶體模型有什麼問題?Java記憶體模型
- Java常見知識點彙總(⑱)——Jvm記憶體結構、Java記憶體模型、Java物件模型的區別JavaJVM記憶體模型物件
- Java面試- JVM 記憶體模型講解Java面試JVM記憶體模型
- JVM的藝術—JAVA記憶體模型JVMJava記憶體模型
- JVM記憶體模型不再是秘密JVM記憶體模型
- 【JVM之記憶體與垃圾回收篇】物件例項化記憶體佈局與訪問定位JVM記憶體物件
- jvm:記憶體模型、記憶體分配及GC垃圾回收機制JVM記憶體模型GC
- JVM問題定位工具JVM
- 如何解決JVM OutOfMemoryError記憶體洩漏問題?JVMError記憶體
- 乾貨分享:淺談記憶體洩露記憶體洩露
- JVM(2)-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- JVM記憶體模型不再是祕密JVM記憶體模型
- Java記憶體模型是什麼,為什麼要有Java記憶體模型,Java記憶體模型解決了什麼問題?Java記憶體模型