1. 程式人生 > >JVM--GC學習

JVM--GC學習

出現問題 遍歷 整理 png 常量 ts包 新增 引用 als

一、為何需要學習GC?

很多人會問為何需要學習GC相關的知識,甚至會問為何學習JVM,最開始我也覺得學習JVM相關知識在工作並沒有多大幫助,很多人包括自己學習的目的可能是為了應付面試。當然有的人說是因為興趣,這個不排除這種可能,不過在中國絕大多數搞IT的僅僅是為了生存,並不是內心中喜歡編程,喜歡搞技術。不過當在工作中遇到內存回收相關的問題的時候,這部分知識如何沒有了解過,就可能會手足無措了。今年4月份左右的時候,所在公司的一個web項目,出現了反應遲鈍的問題,看到同項目組的兩位老大哥通過工具分析tomcat的內存回收情況,診斷出問題到最後如何解決。當時看的時候,只覺得別人很牛逼,自己當時就想過,要是自己遇到這個問題,如果沒了解過JVM內存回收相關的知識,那麽當項目出現問題的,自己會想到往內存方面分析問題?以及自己能解決問題?答案很明顯,是不能。所以我覺得學習JVM這方面的部分知識,有其必要性,當工作中遇到內存相關的問題時候,不說很快解決問題,這需要一個經驗積累的過程,最起碼不至於一臉懵逼。

二、哪些對象可以被回收?

如何確定對象的存活?主要兩種方法:

1.引用計數算法:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收,方法簡單,缺無法解決對象相互循環引用的問題。

2.可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。不可達對象。
在Java語言中,GC Roots包括:

  • 虛擬機棧中引用的對象。
  • 方法區中類靜態屬性實體引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI引用的對象

三、如何回收垃圾對象?

1.標記-清除(Mark-Sweep)算法
標記:標記的過程其實就是,遍歷所有的GC Roots,然後將所有GC Roots可達的對象標記為存活的對象。
清除:清除的過程將遍歷堆中所有的對象,將沒有標記的對象全部清除掉。
它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。

技術分享圖片

2.復制算法
它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然後再把已使用過的內存空間一次清理掉。實現簡單,運行高效。只是這種算法的代價是將內存縮小為原來的一半,持續復制長生存期的對象則導致效率降低。

技術分享圖片

3.標記-整理算法
標記:它的第一個階段與標記/清除算法是一模一樣的,均是遍歷GC Roots,然後將存活的對象標記。
整理:移動所有存活的對象,且按照內存地址次序依次排列,然後將末端內存地址以後的內存全部回收。因此,第二階段才稱為整理階段。

技術分享圖片

4.分代收集算法
Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。詳細過程如下:

技術分享圖片

當new一個對象的時候,優先分配在Eden區,當Eden區滿了的時候,觸發一次minor GC,主要采用復制算法,將存活的對象復制到from Survivor區。

技術分享圖片

當再次new一個對象的時候,發現Eden區滿了,再次觸發Minor GC,註意這裏稍微有點區別,會將Eden區與From Survivor區還在被使用的對象復制到To Survivor區。

技術分享圖片

再下一次Minor GC的時候,則是將Eden區與To Survivor區中的還在被使用的對象復制到From Survivor區。

技術分享圖片

經歷多次Minor GC後,部分Survivor區的對象的年齡達到閥值。這部分對象復制進入老年代

技術分享圖片

當進行Minor GC的時候,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大於老年區的剩余值大小則進行一次Full GC。全局GC成本比較大,如果系統頻繁Full GC,對系統的性能影響非常打。所以需要合理設置年輕代與老年代的大小,盡量減少Full GC的操作。

四、何時進行GC?

1.了解GC時java堆區內存的細分

java堆細分為:年輕代、老年代。年輕代細分:Eden區,伊甸園的意思,對象的起源之地,對象基本上優先在這個區域分配內存。Survivor區,幸存區的意思,意味著當經歷一次GC後,仍然存活的對象從Eden轉移到Survivor區,Survivor區有兩塊:from Survivor和to Survivor。

GC分為:minor GC,普通的對象回收,主要是針對新生代區域的GC;major GC or Full GC,全局GC,主要針對老年代GC,偶爾伴隨著新生代以及對永久代的GC。

2.對象的分配規則

  • 對象優先分配在Eden區,如果Eden區沒有足夠的空間時,虛擬機執行一次Minor GC(次要)。Eden區滿足存活的對象轉移到Survivor區.不滿足存活的對象內存被回收釋放。
  • 大對象直接進入老年代(大對象是指需要大量連續內存空間的對象)。這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代采用復制算法收集內存)。
  • 長期存活的對象進入老年代。虛擬機為每個對象定義了一個年齡計數器,如果對象經過了1次Minor GC那麽對象會進入Survivor區,之後每經過一次Minor GC那麽對象的年齡加1,知道達到閥值對象進入老年區。
  • 動態判斷對象的年齡。如果Survivor區中相同年齡的所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象可以直接進入老年代。
  • 空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大於老年區的剩余值大小則進行一次Full GC,如果小於檢查HandlePromotionFailure設置,如果true則只進行Minor GC,如果false則進行Full GC。

3.GC的時機
當new一個對象,發現需要的內存空間,java堆的Eden區已經無法提供足夠的內存空間的時候,即Eden區滿的時候,觸發Minor GC,通過引用計數或者可達性算法確定Eden區可被回收的對象後,通過復制算法將存活的對象復制到Survivor區,死亡的對象內存空間被釋放。Survivor區存活的對象經歷多次Minor GC後仍然存活的對象,這部分對象進入老年代。當進行Minor GC的時候,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大於老年區的剩余值大小則進行一次Full GC。

JVM--GC學習