1. 程式人生 > >[java,2017-05-15] 內存回收 (流程、時間、對象、相關算法)

[java,2017-05-15] 內存回收 (流程、時間、對象、相關算法)

並不是 pan 處理 大於 新生代 防盜 基礎上 compact full gc

內存回收的流程

java的垃圾回收分為三個區域新生代、老年代、 永久代

技術分享圖片

一個對象實例化時 先去看伊甸園有沒有足夠的空間:
如果有 不進行垃圾回收 ,對象直接在伊甸園存儲;
如果伊甸園內存已滿,會進行一次minor gc;
然後再進行判斷伊甸園中的內存是否足夠;
如果不足 則去看存活區的內存是否足夠;
如果內存足夠,把伊甸園部分活躍對象保存在存活區,然後把對象保存在伊甸園;
如果內存不足,向老年代發送請求,查詢老年代的內存是否足夠;
如果老年代內存足夠,將部分存活區的活躍對象存入老年代.然後把伊甸園的活躍對象放入存活區,對象依舊保存在伊甸園;
如果老年代內存不足,會進行一次full gc,之後老年代會再進行判斷 內存是否足夠,如果足夠 同上;
如果不足 會拋出OutOfMemoryError。

技術分享圖片

內存回收的時間

java中的GC(內存回收)分為2種,minor GC 和 Full Gc(也稱為Major GC)。

Minor GC 的觸發條件:大多數情況下,直接在 Eden 區中進行分配。如果 Eden區域沒有足夠的空間,那就會發起一次 Minor GC;

Full GC(Major GC)的觸發條件:也是如果老年代沒有足夠空間的話,那麽就會進行一次 Full GC。

Ps:上面所說的只是一般情況下,實際上,需要考慮一個空間分配擔保的問題:

在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象的總空間。如果大於則進行Minor GC,如果小於則看HandlePromotionFailure設置是否允許擔保失敗(不允許則直接Full GC)。如果允許,那麽會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,如果大於則嘗試Minor GC(如果嘗試失敗也會觸發Full GC),如果小於則進行Full GC。

但是,具體到什麽時刻執行,這個是由系統來進行決定,是無法預測的。

內存回收的對象

主要根據可達性分析算法,如果一個對象不可達,那麽就是可以回收的;如果一個對象可達,那麽這個對象就不可以回收。

就像現在的汽車需要車牌號,定義一個變量的時候需要先聲明,聲明即可得到一個車牌號,在 new 的時候獲得了一輛車,刪除變量即拿走車牌號,並立即不會回收這輛車。對於沒有車牌的車輛,就是一個不可達的對象,會在垃圾回收的時候回收掉(並不是刪除變量名即回收此變量名所占用的空間)。

內存回收的相關算法

1、標記清除算法Mark-Sweep

這種算法是最簡單最直接的算法,也是其它算法的一些最初思路。標記清除算法其實就是對內存中的對象依次的進行判斷,如果對象需要回收那麽就打一個標記,如果對象仍然需要使用,那麽就保留下來。這樣經過一次叠代之後,所有的對象都會被篩選判(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )斷一次。緊接著會對內存中已經標記的對
象依次進行清除。 這個算法比較簡單粗暴,實現起來比較簡單。
但是會留下兩個比較麻煩的問題:
(1)標記和清除需要兩遍循環內存中的對象,標記本身也是一個比較麻煩的工作,因此這種算法的效率不是特別的高。
(2)對於分配的內存來說,往往是連續的比較好,因為這樣有利於分配大數據的對象。倘若當前內存中都是小段的內存碎片,會知道需要分配大段內存時,沒有可以放置的位置,而觸發內存回收。也就是空間不足而導致頻繁GC和性能下降。

技術分享圖片

2、復制算法Copying

我在使用數據庫的過程中,曾經遇到這樣一個問題,表中的數據量相對來說比較大,大概30萬行,需要使用多個苛刻的條件刪除其中的大部分數據(因此無法使用索引),而只保留其中的較少數據。常規的delete語法使用起來是超時的。於是我查看維護人員的sql,發現一個很有意思的邏輯。首先查出數據庫表中需要保留的數據,放到一張臨時表中。然後徹底刪除掉原有的數據表。然後把這張臨時創建表的表名,改為當初的表名。這是一種典型的空間換取時間的方法。而復制算法就是這樣一個思路。 復制算法中,會將內存劃分為兩塊相等大小的內存區域A/B,然後生成的數據會存放在A區,當A區剩余空間不足以存放下一個新創建的對象時,系統就會將A區中的有效對象全部復制到B區中,而且是連續存放的。然後直接清空A區中的所有對象。 由於編程語言中的對象,大部分在創建後很快就(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )會被回收掉,所以我們需要復制的對象其實並不多。 Java中的實現是這樣的: Java中將Eden和Survivor區同時作為復制算法的使用區域。Survivor又分為From區和To區。這塊內容可以參考我的另外一篇博客,博客中有詳細的介紹:http://www.cnblogs.com/jilodream/p/6147791.html。每次GC的時候都會將Eden和Survivor的From區中的有效對象進行標記,一同復制到Survivor的To區。然後徹底清除原來的Eden區和From區的內存對象。與此同時To區就是下一次回收的From區。

技術分享圖片

復制算法的缺點: 算法使用了空間換取時間的思路,因此需要一塊空白的區域作為內存對象要粘貼的區域。這無疑會造成一種浪費。尤其是內存較小時。 算法每次清除無效對象時,都要進行一次復制粘貼的對象轉移,因此對使用場景是有限制的。只有在有效對象占據總回收內存是非常小的時候,這種算法的性價比才會達到最高。否則大量的復制動作所浪費的時間可能要遠遠大於空間換取時間得到的收益。因此這種算法在Jvm中,也只被用來作為初級的對象回收。因為這時的有效對象比例最低,算法的性價比是最高的。

3、 標記整理算法 Mark-Compact

復制算法需要一塊額外的內存空間,用於存放幸存的內存對象。這無疑造成了內存的浪費。我們還可以在原有的標記清除算法的基礎上,提出了優化方案。也就是標記到的可用對象整體向一側移動,然後直接清除掉可用對象邊界意外的內存。(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )這樣既解決了內存碎片的問題。又不需要原有的空間換時間的硬件浪費。由於老年代中的幸存對象較多,而且對象內存占用較大。這就使得一旦出現內存回收,需要被回收的對象並不多,碎片也就相對的比較少。所以不需要太多的復制和移動步驟。因此這種方法常常被應用到老年代中。

標記整理算法的缺點: 標記整理算法由於需要不斷的移動對象到另外一側,而這種不斷的移動其實是非常不適合雜而多的小內存對象的。每次的移動和計算都是非常復雜的過程。因此在使用場景上,就註定限制了標記整理算法的使用不太適合頻繁創建和回收對象的內存中。

技術分享圖片

4、分代收集算法 Generational Collection

這種算法就是將內存以代的形式劃分,然後針對情況分別使用性價比最高的算法進行處理。在Java中,一般將堆分為老年代和新生代。新創建的對象往往被放置在新生代中。而經過不斷的回收,逐漸存活下來的對象被安置到了老年代中。越新的對象越可能被回收,越老的對象反而會存活的越久。因此針對這兩種場景,新生代和老年代也會分別采用前文所述的兩種算法進行清理。

相關鏈接:

1.https://blog.csdn.net/ni357103403/article/details/51943379

2.https://blog.csdn.net/jidong2622/article/details/78147364

3.https://www.cnblogs.com/jilodream/p/9038853.html

[java,2017-05-15] 內存回收 (流程、時間、對象、相關算法)