Getting started with JVM Memory model and GC

Java 虛擬機具有一個堆,堆是運行時數據區域,所有類實例和數組的內存均從此處分配。


JVM主要管理兩種類型內存:堆和非堆,堆內存(Heap Memory)是在 Java 虛擬機啓動時創建,非堆內存(Non-heap Memory)是在JVM在堆之外的內存。
簡單來說堆是Java代碼可及的內存,留給開發人員使用的;非堆是JVM留給自己用的,包含方法區、JVM內部處理或優化所需的內存(如 JIT Compiler,Just-in-time Compiler,即時編譯後的代碼緩存)、每個類結構(如運行時常數池、字段和方法數據)以及方法和構造方法的代碼。

  •     堆內存(Heap Memory): 存放Java對象
  •     非堆內存(Non-Heap Memory): 存放類加載信息和其它meta-data
  •     其它(Other): 存放JVM 自身代碼等



在JVM啓動時,就已經保留了固定的內存空間給Heap內存,這部分內存並不一定都會被JVM使用,但是可以確定的是這部分保留的內存不會被其他進程使用,這部分內存大小由-Xmx 參數指定。而另一部分內存在JVM啓動時就分配給JVM,作爲JVM的初始Heap內存使用,這部分內存是由 -Xms 參數指定。


在下圖中,我們可以看到在Young Generation中有一個叫Eden Space的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from,to),它們的大小總是一樣,它們用來存放每次垃圾回收後存活下來的對象


首先我們需要知道JVM內存申請過程:

  1.     JVM 會試圖爲相關Java對象在Eden中初始化一塊內存區域
  2.     當Eden空間足夠時,內存申請結束;否則到下一步
  3.     JVM 試圖釋放在Eden中所有不活躍的對象(這屬於1或更高級的垃圾回收),釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區
  4.     Survivor區被用來作爲Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,否則會被保留在Survivor區
  5.     當OLD區空間不夠時,JVM 會在OLD區進行完全的垃圾收集(0級)
  6.     完全垃圾收集後,若Survivor及OLD區仍然無法存放從Eden複製過來的部分對象,導致JVM無法在Eden區爲新對象創建內存區域,則出現”out of memory”錯誤

接下來介紹GC:

以下概念截取自http://www.daniel-journey.com/archives/97

GC:自動內存管理程序,它負責保證被引用的對象始終在內存中;同時把不被應用的對象從內存中釋放。被引用的對象稱之爲Live 對象;不被引用的對象就是Dead對象,是需要回收的。GC會自動計算對象被引用的情況,只要對象不在被引用,相應的內存就會被回收,而C++中需要開發人員通過代碼來“顯示”地回收內存,如果程序員沒有回收就會導致內存的泄露(內存泄露的原因有很多種這只是其中一個)。C++中還有經常出現的一個問題是一個對象在還有其他引用存在的情況下,就被程序給回收了,導致其他引用訪問該對象時出現嚴重錯誤。另外,GC非常重要的一點就避免內存碎片,道理跟windows的磁盤整理一樣,把使用中各個內存塊整合起來,這樣才能保證有足夠的空間來存儲大對象。總而言之,GC試圖使開發者的程序更加有效有序地使用有限的內存。

Hostspot虛擬機採用了“分代回收”的策略,而“分”的非常重要的一個依據就是根據對象存在的時間的長短分成若干個“代(Gerneration)”,每個代上可以採取不同的GC策略。而採用這種“分代回收”策略是利用了2條潛規則,而且這兩條潛規則不只限於Java

  • 絕大對數的對象不會被長時間引用,這些對象在他的“青年期”就會被回收。
  • 幾乎不存在很老和很新對象之間的引用

依據這兩條潛規則,Hotspot分離出新生代(Yound Generation)和舊生代(Old Generation)。由於新生代的空間通常都比較小而且可能存在大量不再被引用的對象,所以針對新生代的GC執行頻率高、速度快。在新生代中存在了一定時間還沒被GC掉的對象最終會被提升到舊生代。舊生代空間比新生代的要大,但它的佔用率增長會比較緩慢,因此,舊生代的GC執行頻率低,但需要更長的時間來完成。

另外還有一個永生代(Permanent Generation),永生代中的保存的對象都是JVM用來方便管理GC的,例如類和方法對象以及它們的描述對象。(Sun的JVM不回收PermGen,由於動態語言產生類會比較多,有時就會出現PermGen Overflow。另外推薦用JRockit JVM)


有了這些基礎,我們就能夠描述GC的執行過程:

  • 在Young Generation塊中,垃圾回收一般用Copying的算法,速度快。每次GC的時候,存活下來的對象首先由Eden拷貝到某個SurvivorSpace, 當Survivor Space空間滿了後,剩下的live對象就被直接拷貝到OldGeneration中去。因此,每次GC後,Eden內存塊會被清空。
  • 在Old Generation塊中,垃圾回收一般用mark-compact的算法,速度慢些,但減少內存要求。
  • 垃圾回收分多級,0級爲全部(Full)的垃圾回收,會回收OLD段中的垃圾;1級或以上爲部分垃圾回收,只會回收Young中的垃圾,內存溢出通常發生於OLD段或Perm段垃圾回收後,仍然無內存空間容納新的Java對象的情況
發佈了27 篇原創文章 · 獲贊 11 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章