JVM內存分配和回收總結

JVM內存分配和回收

本文要求讀者對Java虛擬機(JVM)內部結構比較熟悉,瞭解虛擬機運行時有哪些數據區域,垃圾回收算法以及垃圾回收器。在此基礎上本文對JVM內存分配和回收的相關知識進行梳理和總結。

程序計數器、虛擬機棧、本地方法棧的內存分配與回收

程序計數器、虛擬機棧、本地方法棧三個區域隨線程而生,隨線程而滅;棧中的棧幀隨着方法的進入和退出而有條不紊地執行着出棧和入棧操作。
每一個棧幀中分配多少內存基本上是在類結構確定下來時就已知的,因此這幾個區域的內存分配和回收都具備確定性。
這三個區域不需要過多的考慮回收的問題,因爲方法結束或線程結束時,內存自然就隨着回收了。


Java堆和方法區的內存分配與回收

Java堆中存儲的是實例數據(即對象),主要是成員變量及其值。
方法區中存儲的是類型數據,主要是類信息、常量、靜態變量、即時編譯器編譯後的代碼等。
一個接口中的多個實現類需要的內存可能不一樣,一個方法中的多個分支需要的內存也可能不一樣,JVM只有在程序運行期間才能知道會創建哪些對象,因此Java堆和方法區的內存分配和回收都是動態的,是需要重點討論的地方。

類型數據和實例對象的內存分配

類型數據 ——> 方法區

  • 什麼時候JVM將類加載到方法區?
    • 調用一個類的靜態變量或方法時。
    • 創建一個對象而方法區又沒有相應的類型數據時。

實例對象 ——> 新生代Eden區

  • 新生的對象一般在新生代Eden區分配內存。

實例對象 ——> 老年代

  • 新生的對象進入老年代

    • 若新生的對象太大在新生代Eden區分配失敗,且該對象是一個不含任何引用的數組,則直接在老年代分配,從而避免一次 Minor GC。
    • 若新生的對象比較大,超過了 PretenureSizeThreshold 參數的設置值,則直接在老年代分配內存,從而避免了 Minor GC 時對大對象的複製。(PretenureSizeThreshold 默認值爲0,意味着無論對象多大都只在新生代Eden區分配內存)
  • 新生代的對象進入老年代

    • 若新生代中對象的年齡(經歷 Minor GC 的次數)達到 MaxTenureThreshold 參數設置值,該對象會被移到老年代。
    • Minor GC時,若From Survivor中相同年齡對象達到或超過該區域容量的一半,該區域中大於等於該年齡的對象即使年齡沒有達到 MaxTenureThreshold 參數設置值,也會直接移到老年代。
    • Minor GC失敗時,會觸發空間分配擔保(見附錄),若擔保成功,也會有新生代中的對象移到老年代。

實例對象 ——> 本地線程分配緩存

  • 若啓動了本地線程分配緩存(Thread Local Allocation Buffer, TLAB),則優先在TLAB上爲對象分配內存。

實例對象 ——> 虛擬機棧

  • 若虛擬機檢測到一個對象在運行期間的作用範圍不會超過一個方法或一個線程的作用域(即進行逃逸分析),會把這個對象拆解成其內包含的若干成員變量(即進行標量替換),從而在虛擬機棧上分配內存,節省了Java堆的空間。以上過程是即時編譯技術(Just In Time, JIT)的一種優化情形。

注意:新生代Eden區、老年代和TLAB都在Java堆中。

類型數據和實例對象的內存回收

方法區中的廢棄常量和無用的類是通過Full GC進行回收的。
Java堆中的無用對象是通過Minor GC、Major GC和Full GC進行回收的。

Minor GC

  • 介紹:回收Java堆新生代區域無用對象,新生代回收器採用複製算法。
  • 觸發:當Eden區沒有足夠的空間分配新產生的對象時,虛擬機將發起一次 Minor GC。
  • 過程:Minor GC 觸發後會將新生代Eden區和From Survivor區中存活的對象,複製到To Survivor區。
  • Minor GC失敗:To Survivor區剩餘容量小於新生代Eden區和From Survivor區中存活的對象總容量時,Minor GC失敗,會觸發空間分配擔保(見附錄)。

Major GC

  • 介紹:回收Java堆老年代區域無用對象,老年代回收器大多采用標記-整理算法,而CMS回收器採用標記-清除算法。
  • 觸發:大多數老年代回收器在老年代區域被填滿時會觸發 Major GC,CMS回收器是達到 CMSInitiatingOccupancyFraction 參數設置的佔用率時觸發 Major GC。
  • 過程:老年代回收器大多采用標記-整理算法(CMS回收器採用標記-清除算法)清理老年代區域無用對象。

Full GC

  • 介紹:全面回收Java堆新生代、老年代和方法區(永久代)的無用數據。
  • 觸發
    • 在程序中直接調用System.gc。
    • 方法區(非堆)空間用完時。
    • CMS收集器無法處理浮動垃圾而導致“Concurrent Mode Failure”時。
    • 空間分配擔保(見附錄)過程中可能出現Full GC。
  • 過程:新生代回收器和老年代回收器分別對新生代區域和老年代區域清理無用對象,JVM清除方法區中的廢棄常量並卸載無用的類。

附錄

空間擔保分配過程

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rg7YKpwc-1593533253443)(index_files/_u7A7A_u95F4_u5206_u914D_u62C5_.png)]


參考

[1] 《深入理解Java虛擬機》
[2] -XX:PretenureSizeThreshold的默認值和作用淺析
[3] JVM 觸發Full gc條件
[4] JIT—即時編譯

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