閱讀152 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《JVM故障診斷指南》之4 —— Java 8:從持久代到metaspace

Java 8介紹了一些新語言以及運行時新特點。其中一個特點便是完全移除了持久代(PermGen),自從Oracle公司發布了JDK1.7後就已經宣布了這個決定。還有比如內部字符串,從JDK1.7開始就從持久代移除了,JDK8的發布徹底廢除了它。在這個部分,我們會討論持久代的繼任者:Metaspace。

當執行一個Java程序並出現了“泄露”類元數據對象時我們會比較HotSpot 1.7和HotSpot 1.8的運行時行為的不同點。

一旦Java 8 正式發布,關於Metaspace的最終的參考規範,調優標記以及文檔應該就能使用了。

Metaspace:一個新的內存空間誕生了

JDK8 HotSpot JVM現在使用了本地內存來存儲類元數據,被稱為Metaspace,和Oracle JRockit以及IBM JVM類似。

好消息是它意味著java.lang.OutOfMemoryError:PermGen space問題會越來越少,也不再需要你去調整和監控內存空間。然而這種變化默認是可不見的,接下來我們給你展示的,是你仍然需要關注類元數據內存占用。請記住,這些新特點並不會很神奇的消除類和類加載器的內存泄露。你需要使用不同的方法和學習新的命名約定來找出問題的根源。

總結:

• 持久代場景:
• 這塊內存區域被完全移除。
• PermSize和MaxPermSize JVM 參數會被忽略,並且在啟動的時候會給出警告信息。

• Metaspace 內存分配模型
• 對於類元數據的大多數內存分配都不會發生在本地內存。
• 被用於描述類元數據的類對象被移除。

•Metaspace 容量
• 默認的,類元數據分配限製於可用的本地內存 (容量大小依賴於你用32位jvm或者64位jvm的操作係統可用虛擬內存)。
• 新的標記已經可以使用 (MaxMetaspaceSize),它允許你限製用於類元數據的本地內存大小。如果你沒有指定這個標記,Metaspace會根據運行時應用程序的需求來動態的控製大小。

•Metaspace 垃圾收集
• 一旦類元數據的使用量達到了“MaxMetaspaceSize”指定的值,對於無用的類和類加載器,垃圾收集此時會觸發。
• 為了控製這種垃圾收集的頻率和延遲,合適的監控和調整Metaspace非常有必要。過於頻繁的Metaspace垃圾收集是類和類加載器發生內存泄露的征兆,同時也說明你的應用程序內存大小不合適,需要調整。

•Java 堆空間影響
一些雜項數據被移到了Java堆空間。這意味著當你更新到JDK8後會觀察到Java堆空間的增長。

•Metaspace 監控
• Metaspace 的使用可以通過HotSpot 1.8的詳細的GC日誌輸出觀察到。
• 在基於b75上測試的時候Jstat 和 JVisualVM 還沒有更新,舊的持久代空間引用依然存在。

足夠的理論知識就介紹到這,讓我們在行動中通過會發生泄露的Java程序來看看新的內存空間…

持久代 vs. Metaspace運行時比較

為了能更好的理解新的metaspace內存空間在運行時的行為,我們創建了一個會發生元數據泄露的java程序。你可以從這裏下載(準備梯子)。

下麵的場景將會被測試:
• 使用JDK1.7運行這個Java程序,目的是為了監控和消耗設置好的128M持久代空間。
• 使用JDK1.8(b75)運行這個Java程序,目的是為了監控Metaspace內存空間的動態增長和垃圾收集。
• 使用JDK1.8(b75)運行這個Java程序,設置MaxMetaspaceSize為128M,目的是為了模擬Metaspace空間的消耗。

JDK 1.7 @64-bit – 持久代消耗
• 一個包含5萬個配置好的迭代的程序
• 1024M的java堆
• 128M java持久代(-XX:MaxPermSize=128m)

這裏寫圖片描述

從JVisualVM裏可以看到,持久代的消耗在加載了超過3萬個類之後幾乎達到了臨界。我們也可以從Java程序和GC輸出中看到這種消耗。
這裏寫圖片描述
現在讓我們用HotSpot JDK 1.8 來執行這個程序。

JDK 1.8 @64-bit – Metaspace 動態大小
• 一個包含5萬個配置好的迭代的程序
• 1024M的堆
• Java Metaspace空間:無限(默認)
這裏寫圖片描述 
這裏寫圖片描述

從詳細的GC輸出可以看到,JVM的metaspace的確動態的把本地內存從20M擴展到了320M,目的是為了適應增長的Java程序中類元數據的內存占用。我們也可以觀察到JVM會嚐試進行垃圾收集的事件,目的是為了消滅無用的類和類加載器對象。自從我們的Java程序開始泄露內存,JVM沒有選擇,隻能動態擴展Metaspace內存空間。

這個程序可以運行5萬次迭代而不會發生OOM事件,並且加載了超過5萬個類。

讓我們轉移到我們最後一次測試場景:

JDK 1.8 @64-bit – Metaspace 消耗
• 一個包含5萬個配置好的迭代的程序
• 1024M的堆
• Java Metaspace空間:128 MB (-XX:MaxMetaspaceSize=128m)
這裏寫圖片描述

從JVisualVM裏可以看到,在加載了超過3萬個類後,Metaspace消耗達到了臨界,和用JDK1.7運行的結果類似。我們可以從程序和GC輸出中看到這個結果。另一個有意思的觀察結果是本地內存占用是指定最大值的2倍。這或許可以說明,一種好的調整metaspace擴容的策略有可能避免本地內存的浪費。

和用JDK1.7運行一樣,我們指定了metaspace最大容量為128M,但它在我們程序裏並不能完成5萬次的迭代。新的OOM會被拋出。上麵的OOM事件是在內存分配失敗後由JVM從metaspace裏拋出的。.
關於metaspace的總結

目前觀察到的結果完全說明了合適的監控和調優是非常必要的,目的是為了盡量避免類似我們最後一種測試場景中過多的metaspace GC或者OOM觸發的問題。

譯者介紹:
梅小西
Java工程師,關注JVM,並發編程,喜歡研究Python,Scala,Golang等。

譯者相關譯文:
JVM內部原理
《JVM故障診斷指南》之1 ——JVM概覽與介紹
《JVM故障診斷指南》之2 ——調整合適的Java堆大小的技巧
《JVM故障診斷指南》之3 ——Java 線程: JVM持有內存的分析
《JVM故障診斷指南》之4 ——Java 8:從持久代到metaspace

最後更新:2017-05-22 15:34:20

  上一篇:go  過分依賴大數據讓樂高麵臨破產,讓其轉危為安的竟是一雙舊鞋中的小數據
  下一篇:go  軟件事務內存導論