3-JVM的GC算法(1)

GC算法概述:

JAVA語言最大的特點在於具備良好的垃圾收集特點,也就是GC是整個java之中最重要的安全保證,幫助開發者寫出合理的代碼。整個JVM中的GC處理機制:對不需要的對象進行標記,而後進行清除。

 

堆內存的劃分

圖一:java堆內存的劃分

 

1.8之後永久代改成了元空間 永久代只有HotSpot中有  Oracle 試圖將HotSpot和JRockit的標準合併爲一個

JVM中堆內存之中實際內存分三塊

1、 新生代:新對象和沒有達到一定年齡的對象都在新生代

2、 老年代:被長時間使用的對象,內存空間比

3、 元空間:一些方法中臨時操作的對象等,直接使用物理內存(外部內存);

最初的永久代需要在JVM堆內存中進行劃分(內部內存);

分區的目的是方便內存的分類管理

GC處理的主要區域是新生代和老年代,元空間(永久代)都不在GC處理範圍內。

 

GC對象處理流程

在開發中會有臨時對象和常駐對象爲了保證GC的性能問題對於GC對象的處理流程如圖

圖二:GC的策略

 

新對象產生,對象需要申請空間 

1、  爲新對象申請空間  Eden區是否足夠,足夠 分配;不足,Minor GC回收Eden中的不活躍對象 回收後再次判斷Eden區空間是否充足,充足 分配;不足 轉2;

2、 判斷 Survivor空間是否充足,充足 把Eden區的部分活躍對象保存到Survivor區,Eden區有了空閒空間,分配給新對象;否則 轉3

3、 判斷 Tenured空間是否充足,充足:把Survivor區的部分活躍對象保存到Tenured區,把Eden區的部分活躍對象保存到Survivor區,Eden區有了空閒空間,分配給新對象;否則進行Full GC,JVM進行完全垃圾回收,接着判斷Tenured區空間充足,如果充足:重複上述的對象移動過程;否則:拋出「OutOfMemoryError」異常。

 

伸縮區

圖三:伸縮區

 在堆內存的分區中都存在一部分可變伸縮區,其基本使用流程是:如果內存不足了,則在可變的範圍之內擴大內存空間,當內存不在緊張的時候再將可變內存空間釋放,但是擴充和收縮需要消耗資源

內存調優就是減少收縮和擴充過程,幹掉伸縮區,最簡單的調優只會調整兩個參數「-Xmx」(最大內存)和「-Xms」(初始化內存),通過調整着兩個參數,讓:最大內存=初始化內存,這樣就沒有伸縮區了,從而提高內存使用效率。

 

GC策略

首先介紹根的概念:

   1、棧中引用的對象

   2、方法區中靜態成員或者常量引用的對象(全局對象)

   3、JNI方法中引用的對象

年輕代的GC策略

所有對象都產生在年輕代的Eden區  如果空間不足則會引發Minor GC 和Major GC(Full GC)所有的關鍵字new新實例化的對象一定會保存在Eden區,而對於存活區保存的一定是在Eden中長時間存在並且經過了好幾次的Minor GC還保存下來的活躍的對象,這個對象會晉升到Survivor區中;

Survivor區一定會有兩塊空間且空間大小相對:一塊是爲了晉升,一塊爲了對象回收,這兩塊內存空間中一定有一塊是空的。年輕代GC算法的Survivor區實現是複製算法:從根集合掃描出存活對象,並找到存活對象複製到一塊新的完全未使用的空間中,然後進行空間合併(主要是存活區),騰出新的完全未使用的空間。

 

圖四:年輕代GC策略

從算法上可以看出,在Survivor區分爲S1和S0,兩者大小相等且總有一個空的,所以這個是浪費的空間。

 

另一個問題就是,在我們寫代碼時間,會有很多臨時對象,那麼Eden區中可能會保存大量的臨時對象就會導致頻發的Minor GC操作,在HotSpot虛擬機中爲了加快此空間的內存分配採用了兩種技術「Bump-The-Pointer」和「TLAB(Thread-Local-Allocation Buffers)」

 

Bump-The-Pointer:這個算法把Eden看成一個棧空間,所有的線程每個申請的堆對象都直接壓棧,也可以出棧。

圖五:Bump-The-Pointer

 

 

TLAB(Thread-Local-AllocationBuffers):TLAB算法還是採用的壓棧和出棧,只是把Eden區分爲多個,每個線程都有一個獨立的私有棧區,用於存儲對應線程所申請的堆對象。

圖六:TLAB(Thread-Local-AllocationBuffers)

 

 

老年代的GC策略

老年代主要接收由年輕代發送過來的對象,一般情況下是經過好幾次的Minor GC之後還會保存下來的對象纔會進入老年代,如果保存的對象超過了Eden區的大小,那麼此對象也會直接保存到老年代中,當老年代內存不足時會引發「Full GC」(Major GC)內存最大

老年代的GC算法是兩種方式的結合:整理和壓縮

 

標記-清除:在回收清除的過程中,所有在老年代中被回收的對象並沒有對空間進行整理,老年代最頭疼的問題是碎片化問題類似操作系統中的連續動態內存分配。

圖七:標記-清除

 

標記-壓縮:把各個碎片化的空閒空間移動到一起,形成一個大的連續內存空間,但是合併碎片化內存需要消耗大量資源

圖八:標記-壓縮

 

所有在進行老年代存儲的時候儘可能保存長期會被使用,不會被輕易回收的大對象

 

永久代

永久代保存在堆內存之中,但是不會被回收,例如intern()方法產生的對象不會被回收。如果操作不當導致永久代中的數據量過大,一樣會拋出「OutOfMemoryError」

 

元空間

元空間是在JDK1.8之後纔有,和永久代沒什麼區別,唯一區別是永久代是堆內存的,而元空間是物理內存,直接受到操作系統的控制。