java面試進階26:如何監控和診斷JVM堆內和堆外內存使用?

堆內空間:

堆內分析工具:圖形化工具,如 JConsole、VisualVM(jmc,jconsole在linux上用不了的問題,其實1可以遠程連接,2可以使用xshell);命令行工具進行運行時查詢,如 jstat 和 jmap 等工具;Tomcat、Weblogic內置功能;GC 日誌

1、新生代:內部又分爲 Eden 區域,作爲對象初始分配的區域;兩個 Survivor,有時候也叫 from、to 區域,被用來放置從 Minor GC 中保留下來的對象。JVM 會隨意選取一個 Survivor 區域作爲“to”,然後會在 GC 過程中進行區域間拷貝,也就是將 Eden 中存活下來的對象和 from 區域的對象,拷貝到這個“to”區域。這種設計主要是爲了防止內存的碎片化,並進一步清理無用對象。

Hotspot JVM 還有一個概念叫做 Thread Local Allocation Buffer(TLAB)TLAB 仍然在堆上,它是分配在 Eden 區域內的。其內部結構比較直觀易懂,start、end 就是起始地址,top(指針)則表示已經分配到哪裏了。所以我們分配新對象,JVM 就會移動 top,當 top 和 end 相遇時,即表示該緩存已滿,JVM 會試圖再從 Eden 裏分配一塊兒。爲了避免多線程同時分配內存時操作同一地址。

2、老年代:放置長生命週期的對象,通常都是從 Survivor 區域拷貝過來的對象。如果對象較大,JVM 會試圖直接分配在 Eden 其他位置上;如果對象太大,完全無法在新生代找到足夠長的連續空閒空間,JVM 就會直接分配到老年代。

3. 永久代:儲存 Java 類元數據、常量池、Intern 字符串緩存,在 JDK 8 之後就不存在永久代這塊兒了。

參數配置:

最大堆體積-Xmx value     初始的最小堆體積 -Xms value 

老年代和新生代的比例 -XX:NewRatio=value,默認情況下,這個數值是 2,意味着老年代是新生代的 2 倍大;換句話說,新生代是堆大小的 1/3。新生代的大小-XX:NewSize=value

Survivor 區域就是 Eden 的 1/8 大小,也就是新生代的 1/10,因爲 YoungGen=Eden + 2*Survivor,JVM 參數格式是-XX:SurvivorRatio=value

如果 Xms 小於 Xmx,堆的大小並不會直接擴展到其上限,也就是說保留的空間(reserved)大於實際能夠使用的空間(committed)。當內存需求不斷增長的時候,JVM 會逐漸擴展新生代等區域的大小,所以 Virtual 區域代表的就是暫時不可用(uncommitted)的空間。

堆外空間:

堆外 NMT 特性對 JVM 進行分析,會打印如下圖信息


依次打印

Java 堆、

 Class 內存佔用,它所統計的就是 Java 類元數據所佔用的空間、

 Thread,這裏既包括 Java 線程,如程序主線程、Cleaner 線程等,也包括 GC 等本地線程、(JDK 9 的默認 GC 是 G1,雖然它在較大堆場景表現良好,但本身就會比傳統的 Parallel GC 或者 Serial GC 之類複雜太多,所以要麼降低其並行線程數目,要麼直接切換 GC 類型;)

Code 統計信息,顯然這是 CodeCache 相關內存,關閉JIT compiler的TieredCompilation,使用情況下降

GC 部分了

Compiler 部分,就是 JIT 的開銷

Internal(JDK 11 以後在 Other 部分)部分,其統計信息包含着 Direct Buffer 的直接內存,這其實是堆外內存中比較敏感的部分,很多堆外內存 OOM 就發生在這裏

 

程序的方式而不是工具,對 Java 內存使用進行監控,有哪些技術可以做到?      java.lang.Runtime類有freeMemory()、totalMemory()等方法可以獲取到jvm內存情況,看了一下是本地方法。

爲什麼在標記垃圾的時候,需要stop the world?是爲了避免在標記的時候又有對象在堆內生成,如果這個對象對其他未標記對象有引用,而這個時候由於gc而清理掉了未標記的對象,會有問題,不過以cms爲例,它有不同的mark: initial mark,conc mark, remark;conc時候不需要stw;其他需要短暫stw,這樣引用關係纔不變,另外效率也高

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章