Java虛擬機-垃圾回收簡介

一、如何判定對象爲垃圾對象

-verbose:gc  打印垃圾回收簡單信息參數
-xx:+PringDCDetail 打印垃圾回收的詳細信息

引用計數法

引用計數算法很簡單,它實際上是通過在對象頭中分配一個空間來保存該對象被引用的次數。如果該對象被其它對象引用,則它的引用計數加一,如果刪除對該對象的引用,那麼它的引用計數就減一,當該對象的引用計數爲0時,那麼該對象就會被回收。

採用引用計數的垃圾收集機制中,垃圾收集的開銷被分攤到整個應用程序的運行當中了,而不是在進行垃圾收集時,要掛起整個應用的運行,直到對堆中所有對象的處理都結束。因此,採用引用計數的垃圾收集不屬於嚴格意義上的"Stop-The-World"的垃圾收集機制。

引用計數算法有一個比較大的問題,那就是它不能處理環形數據,即如果有兩個對象相互引用,那麼這兩個對象就不能被回收,因爲它們的引用計數始終爲1。這也就是我們常說的“內存泄漏”問題。

Python中採用的是引用計數機制爲主,標記-清除和分代收集兩種機制爲輔的策略。

可達性分析法

爲了引用計數算法中循環引用導致垃圾不會被回收的問題,在Java中採取了 可達性分析法。同樣採用此法的還有C#、Lisp(最早的一門採用動態內存分配的語言)。該方法的基本思想是通過一系列的“GC Roots”對象作爲起點進行搜索,如果在“GC Roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的,不過要注意的是被判定爲不可達的對象不一定就會成爲可回收對象。被判定爲不可達的對象要成爲可回收對象必須至少經歷兩次標記過程,如果在這兩次標記過程中仍然沒有逃脫成爲可回收對象的可能性,則基本上就真的成爲可回收對象了。

GC Roots的對象

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象。(可以理解爲:引用棧幀中的本地變量表的所有對象)
  • 方法區中靜態屬性引用的對象(可以理解爲:引用方法區該靜態屬性的所有對象)
  • 方法區中常量引用的對象(可以理解爲:引用方法區中常量的所有對象)
  • 本地方法棧中(Native方法)引用的對象(可以理解爲:引用Native方法的所有對象)

二、如何回收

JVM的內存分代劃分

Java虛擬機將堆內存劃分爲新生代老年代永久代。

新生代(Young)

HotSpot將新生代劃分爲三塊,一塊較大的Eden空間和兩塊較小的Survivor空間,默認比例爲8:1:1。劃分的目的是因爲HotSpot採用複製算法來回收新生代,設置這個比例是爲了充分利用內存空間,減少浪費。新生成的對象在Eden區分配(大對象除外,大對象直接進入老年代),當Eden區沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC。

老年代(Old)

在新生代中經歷了多次(具體看虛擬機配置的閥值)GC後仍然存活下來的對象會進入老年代中。老年代中的對象生命週期較長,存活率比較高,在老年代中進行GC的頻率相對而言較低,而且回收的速度也比較慢。

永久代(Permanent)

永久代存儲類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,對這一區域而言,Java虛擬機規範指出可以不進行垃圾收集,一般而言不會進行垃圾回收。

回收策略

1.標記-清除算法(Mark-Sweep)

算法分爲“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象。

標記-清除算法的主要缺點:

  • 效率問題:標記和清除過程的效率都不高;
  • 空間問題:標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致,碎片過多會導致大對象無法分配到足夠的連續內存,從而不得不提前觸發GC,甚至Stop The World。

2.複製算法(Copying)

爲解決效率問題,“複製”收集算法出現了。它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。
這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
它的主要缺點有兩個:

  • 效率問題:在對象存活率較高時,複製操作次數多,效率降低;
  • 空間問題:內存縮小了一半;需要使用老年代的額外空間做分配擔保。

 From SurvivorTo Survivor使用的就是複製算法。老年代不使用這種算法。

3.標記-整理算法

複製收集算法在對象存活率較高時就要執行較多的複製操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。    
根據老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。也稱爲標記-整理-清除算法。

4.分代收集算法(Generational Collection)

GC分代的基本假設:絕大部分對象的生命週期都非常短暫,存活時間短。
“分代收集”算法,把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。

參考資料:

  1. https://www.jianshu.com/p/1d5fa7f6035c
  2. https://blog.csdn.net/luzhensmart/article/details/81431212
  3. https://blog.csdn.net/wuzhiwei549/article/details/80563134
  4. https://www.cnblogs.com/fangfuhai/p/7206944.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章