天天看點

JVM--Heap堆詳解

堆(Heap)

一個JVM執行個體隻存在一個堆記憶體,堆記憶體的大小是可以調節的。

類加載器讀取了類檔案後,需要把類、方法、常變量放到堆記憶體中,儲存所有引用類型的真是資訊,以友善器執行,堆記憶體分為3部分:

  • Yong Generation Space 新生區: Young/New
  • Tenure Generation Space 養老區:Old/Tenure
  • Permanent Space 永久區:Perm

Java7之前:

JVM--Heap堆詳解

Java8:

JDK1.8之後為metaspace替代永久代:

JVM--Heap堆詳解

新生區是類的誕生、成長、消亡的區域,一個類在這裡産生,應用,最後被垃圾回收器收集,結束生命。新生區又分為兩部分:伊甸區(Eden Space)和幸存者區(Survivor space),所有的類都是再伊甸區被new出來的。

幸存區又兩個:0區(Survivoa 0 Space)和1區(Survivor 1 space)。當伊甸的空間用完時,程式又需要建立對象,JVM的垃圾回收器将對伊甸區進行垃圾回收(Minor GC),将伊甸區中的不再被其他對象所引用的對象進行銷毀。然後将伊甸區中的剩餘對象移動到幸存0區。若幸存0區也滿了,再對該區進行垃圾回收,然後移動到1區。如果1區也滿了,再移動到養老區。

若養老區也滿了,那麼這個時候将産生Major GC(Full GC),進行養老區的記憶體清理。若養老區執行了FUll GC之後發現依然無法進行對象的儲存,就會産生OOM異常『OutOfMemoryError』。

如果出現java.lang.OutOfMemoryError:Java heap space異常,說明Java虛拟機的堆記憶體不夠,原因有兩點:

  • Java虛拟機的堆記憶體設定不夠,可以通過參數-Xms、-Xmx來調整。
  • 代碼中建立了大量大對象,并且長時間不能被垃圾收集器收集(存在被引用)。

Java堆從GC的角度還可以細分為:新生代(Eden區、From Survivor區和To Survivor區) 和老年代。

JVM--Heap堆詳解

MinorGC的過程(複制-》清空-》互換):

  1. eden、SurvivorFrom複制到SurvivorTo,年齡+1

    首先,當Eden區滿的時候會觸發第一次GC,把還活着的對象拷貝到SurvivorFrom區,當Eden區再次出發GC的時候會掃描Eden區和From區域,對這兩個區域進行垃圾回收,經過這次回收後還存貨的對象,則直接複制到To區域(如果有對象年齡已經達到了老年的标準,則複制到老年代),同時把這些對象的年齡+1.

  2. 清空eden、SurvivorFrom:

    然後,清空Eden和SurivorFrom中的對象,也即複制之後有交換,誰空誰是to。

  3. SurvivorTo和SurvivorFrom互換:

    随後,SurvivorTo和SurvivorFrom互換,原SurvivorTo成為下一次GC時的SurvivorFrom區。部分對象會在From和To區域中複制來複制區,如此交換15次(由JVM參數MaxTenuringThreshold決定,這個參數預設為15),最終如果還是存活,就存入到老年代。

HotSpot記憶體管理

分代管理:

JVM--Heap堆詳解

因為:不同對象的生命周期不同,98%的對象是臨時對象。

實際而言,方法區(Method Area)和堆一樣,是各個線程共享的記憶體區域,它用于存儲虛拟機加載的:類資訊+普通常量+靜态常量+編譯器編譯後的代碼等等,雖然JVM規範将方法區描述為堆的一個邏輯部分,但它卻還有一個别名叫Non-Heap(非堆),目的就是要和堆分開。

對于HotSpot虛拟機,很多開發者習慣将方法區稱之為『永久代』(Parmanent Gen),但嚴格本質上說兩者不同,或者說使用永久代來實作方法區而已,永久代是方法區(相當于是一個接口interface)的一個實作,jdk1.7的版本中,已經将原本放在永久代的字元串常量池移走。

JVM--Heap堆詳解

永久區(java7之前):

永久存儲區是一個常駐記憶體區域,用于存放JDK自身所攜帶的Class,Interface的中繼資料,也就是說它存儲的是運作環境必須的類資訊,被裝載進此區的資料是不會被垃圾回收器回收掉的,關閉了JVM才會釋放此區所占用的記憶體。