1. 程式人生 > >JVM02----垃圾收集上(堆)

JVM02----垃圾收集上(堆)

Java中最大的特點在於具備良好的垃圾收集器。GC是JAVA中最重要的安全保證。

整個JVM中的GC的處理機制:對不需要的物件進行標記,而後進行清除。

一. 堆記憶體劃分

                          

note:(1.8之前元空間的位置是永久代,這是最大的變化)。JDK1.8開始,之前的永久代空間取消了。取消永久代的目的是為了將HotSpot與JRocket兩個虛擬機器標準合成一個,因為以前只有HotSpot才有永久代。

在整個的JVM堆記憶體之中實際上將記憶體分為了三塊:

  • 年輕代:新物件和沒達到一定年齡的物件都在年輕代;(比如18歲前)

           新生物件在Eden,而年齡達到一定的在SO或者S1,還有伸縮區域可以擴充套件;

  • 年老代:物件被長時間使用的物件。(比如18歲到80歲),老年代的記憶體空間應該比年輕代的記憶體空間更大。
  • 元空間:像一些方法中的操作臨時物件等,直接使用實體記憶體;

           最初的永久代是需要在JVM堆記憶體裡面進行劃分;而元空間直接使用實體記憶體;

為何分割槽:

是為了更好的進行每一塊的管理(好比磁碟分割槽管理),可以確定哪一塊記憶體可以被清空,哪一塊不能。

二. GC流程

所有的資料都會儲存在JVM的堆記憶體之中,但是在實際的開發中經常會建立許多的臨時物件,也會有一些常駐物件在(比如單例),所以為了保證GC的效能問題,對於GC的處理流程如下圖所示:

對於GC流程裡面,那麼最需要處理的就是年輕代與老年代記憶體清理操作,而元空間(永久代)都不在GC範圍內。其實元空間和永久代差不多,只是一個是內部記憶體,一個是外部記憶體。

                          

      圖的說明如下:

  • 當現在有一個新的物件產生(Eden),那麼物件一定需要記憶體空間,於是現在就需要為該物件進行記憶體空間的申請。
  • 首先會判斷Eden園區是否有記憶體空間,如果此時有記憶體空間,則直接將新物件儲存在Eden區域;如果此時Eden記憶體空間不足,那麼他會出發一個Minor GC操作,將Eden的無用記憶體空間進行清理。(分割槽的好處不會影響到別的區域);清理之後會繼續判斷Eden的記憶體空間是否充足。充足的話則將新的物件直接在Eden進行空間分配;如果執行了Minor GC後,Eden記憶體任然不足,那麼這個時候會進行存活區的記憶體判斷,則會將Eden的部分物件存在存活區,(Eden就會空出一部分空間出來了),那麼這個時候又繼續判斷Eden的空間是否夠,夠就存。  如果存活區也沒有記憶體空間了,那麼就繼續判斷老年區。如果此時老年區的空間充足,則將存活區中的活躍物件儲存到老年代,而後存活區就會出現空餘空間,隨後Eden將活躍物件儲存到存活區之中,從而為Eden開闢記憶體空間。--------------說明所有的新物件都會在Eden區。
  • 如果這個時候老年代也滿了,那麼這個時候將產生Major GC(Full GC),那麼這個時候就會進行老年代的垃圾清理。
  • 如果老年代執行了Full GC之後發現仍然無法進行物件的儲存,就會產生OOM異常,OutOfMemoryError.

 三. JVM記憶體調整引數(調優關鍵)

通過之前的分析發現,實際上每一塊子記憶體區域中都會存在有一部分的可變伸縮區域,其基本流程為:如果空間不足,則在可變的空間範圍內,擴充套件記憶體空間;當一段時間內,發現記憶體空間不那麼緊張了,因此開始收縮記憶體空間。(因為判斷伸縮是花時間的,因此它是調優的關鍵,我們應該儘量讓這個伸縮區域不存在)。

               

前兩個最常用。

在整個堆記憶體的調整策略之中,有經驗的人基本上都只會調整兩個引數(前兩個):一個叫做最大記憶體,一個叫做初始化記憶體如果讓這兩個相等,那麼就沒有可變的範圍,效能就可以提升。如果要想取得這些記憶體的整體資訊直接利用Runtime類即可。

上面的單位是位元組,最好用M單位來表示,就除以兩個1024

 

根據結果會發現:預設情況下分配的記憶體是總記憶體的“1/4”(上面的總記憶體是32G).而初始化的記憶體為1/64.那麼伸縮區就是491.0M到7276.5M之間,那麼現在就有可能造成程式的效能下降。那麼最好的做法就是讓這兩個引數一樣大小。那麼這個時候就避免了伸縮去的可調策略,從而提升了整個程式的效能。

比如這樣:

 

note:在eclipse裡面可以配置的,比如開啟Run configuratios----VM arguments

 四. 年輕代

所有的新物件都會在年輕代產生。如果年輕代的空間的不足,無法生成物件,則會引發Minor GC和Major GC(Full GC)。

 

 

所有使用關鍵字new新例項化的物件一定會在Eden儲存,而存活區儲存的一定是已經在Eden中存在好久並且經過了好幾次的Minor GC還儲存下來的存活物件。那麼這個物件將晉升到存活區之中。存活區分為兩個存活區,而且這兩個存活區一定是相等的大小。目的:一塊存活區為了晉升,另外一塊為了物件回收。Fromto不固定,是會互相換的。這兩塊記憶體空間一定有一塊是空的。

 Minor GC的演算法如下圖:

在年輕代中使用的是Minor GC,這種GC演算法採用的是複製演算法。

解釋:當Eden空間不足的時候,會把存活物件上升到存活區中,即把綠色放到存活區中的。當存活區中的GC完了以後,將統一把存活物件放到一個存活區中,另外一個存活區就會被清空。

通過以上的分析可以發現,在整個的處理過程中,Eden中大多說都是臨時的新物件,可能會發生頻繁的Minor GC,所以在HotpSpot虛擬機器之中為了加快此空間的記憶體分配,所以採用了兩種技術優化實現:Bump-The-Pointer和Thread-LocalAllocation Buffers.

五. 老年代

老年代主要是接收由年輕代發來的物件。一般是經過了好幾次Minor GC後的。如果你要儲存的物件超過了Eden大小,那麼這個物件可以直接儲存到老年代。當老年代記憶體不足時,將引發Full GC.(Major GC)

老年代中的演算法有2個:

在回收清除的過程之中,發現所有在老年代被回收的物件並沒有進行空間的整理,所以老年代裡面最頭太疼的問題就是碎片問題。於是還有第二種演算法:

所以:以後在進行老年代儲存的時候,儘可能儲存長期會被使用的物件,並且不會被輕易回收的大物件的存在。

 六. 永久代(1.8之後消失了)-------取而代之的是元空間

雖然SEJDK1.8,但是JavaEE還是JDK1.7呢.

永久代時在堆記憶體之中儲存的,但是永久代不會被回收,比如inter()方法產生的物件,是不會被回收的。如果操作不當,導致永久代中的資料過大,這個時候程式會丟擲OOM異常。

七. 元空間

唯一的區別是永久代使用的堆記憶體空間,而元空間時使用的實體記憶體,直接受到本地的實體記憶體限制。

 

 

 

 

參考文獻:

https://blog.csdn.net/SEU_Calvin/article/details/51404589

《深入理解JAVA虛擬機器》

李興華老師的《Java記憶體模型》