垃圾檢測通常通過建立一個根對象的集合以及建立一個從這些根對象開始能夠觸及的對象集合來實現。如果正在執行的程序可以訪問到根對象和某個對象之間存在引用路徑,這個對象就是可觸及的。對于程序來說,根對象總是可以訪問的。從這些根對象開始,任何可以被觸及的對象都被認為是“活動”的對象。無法被觸及的對象被認為是垃圾。
虛擬機的根對象集合根據實現不同而不同,包含局部變量中的對象引用和棧幀的操作數棧(以及類變量中的對象引用)、被加載的類的常量池中的對象引用(比如字符串)、傳遞到本地方法中的沒有被本地方法釋放的對象引用。任何被根對象引用的對象都是可觸及的,從而是活動的,任何被活動的對象引用的對象都是可觸及的,程序可以訪問任何可觸及的對象,所以這些對象應用留在堆中,而對于那些不可觸及的對象,程序沒有辦法訪問它們,應該被收集和釋放。
區分活動對象和垃圾的兩個基本方法是引用計數和跟蹤。引用計數垃圾收集器通過為堆中的每個對象保存一個計數來區分活動對象和垃圾對象。這個計數記錄下這個對象的引用次數。跟蹤垃圾收集器實際上追蹤從根節點開始的引用圖。在追蹤中遇上的對象以某種方式打上標記,當追蹤結束時,沒有被打上標記的對象就被判定是不可觸及的,可以被當作垃圾收集。
==引用計數算法==
給對象添加一個引用計數器,每當一個地方引用它時,數據器加1;當引用失效時,計數器減1;計數器為0的即可被回收。
- 優點:實現簡單,判斷效率高
- 缺點:很難解決對象之間的相互循環引用(objA.instance = objB; objB.instance = objA)的問題,所以java語言并沒有選用引用計數法管理內存
==根搜索算法==
Java和C#都是使用根搜索算法來判斷對象是否存活。通過一系列的名為“GC Root”的對象作為起始點,從這些節點開始向下搜索,搜索所有走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時(用圖論來說就是GC Root到這個對象不可達時),證明該對象是可以被回收的。
在Java中這些對象可以成為GC Root:
- 虛擬機棧(棧幀中的本地變量表)中的引用對象
- 方法區中的類靜態屬性引用的對象
- 方法區中的常量引用對象
- 本地方法棧中JNI(即Native方法)的引用對象
==標記-清除算法==
標記-清除算法是一種常見的基礎垃圾收集算法,它將垃圾收集分為兩個階段
- 標記階段:標記出可以回收的對象。
- 清除階段:回收被標記的對象所占用的空間。

63707281.jpeg
標記-清除算法主要有兩個缺點,一個是標記和清除的效率不高,另一個從圖中就可以看出,就是容易產生大量不連續的內存碎片,碎片太多可能會導致后續沒有足夠的連續內存分配給較大的對象,從而提前觸發新的一次垃圾收集動作。
==復制算法==
為了解決標記-清除算法的效率不高的問題,產生了復制算法。它把內存空間劃分為兩個相等的區域,每次只使用其中一個區域。在垃圾收集時,遍歷當前使用的區域,把存活對象復制到另一個區域中,最后將當前使用的區域的可回收的對象進行回收。

90984624.jpeg
這種算法每次都對整個半區進行內存回收,不需要考慮內存碎片的問題,代價就是使用內存為原來的一半。復制算法的效率與存活對象的數目多少有很大的關系,如果存活對象很少,復制算法的效率就會很高。由于絕大多數對象的生命周期很短,并且這些生命周期很短的對象都存于新生代中,所以復制算法被廣泛應用于新生代中。
==標記-壓縮算法==
在新生代中可以使用復制算法,但是在老年代就不能選擇復制算法,因為老年代對象存活率會較高,這樣會有較多的復制操作,導致效率變低。標記-清除算法可以應用在老年代中,但是效率不高,在內存回收后容易產生大量內存碎片。因此就出現了一種標記-壓縮算法,與標記-清除算法不同的是,在標記可回收的對象后將所有存活的對象壓縮到內存的一端,使它們緊湊地排列在一起,然后對邊界以外的內存進行回收,回收后,已用和未用的內存都各自一邊。

94057049.jpeg
標記-壓縮算法解決了標記-清除算法效率低和容易產生大量內存碎片的問題,它被廣泛應用于老年代中。
==分代收集算法==
分代收集算法會結合不同的收集算法來處理不同的空間,因此在學習分代收集算法之前我們首先要了解Java堆區的空間劃分。Java堆區的空間劃分在Java虛擬機中,各種對象的生命周期會有著較大的差別,大部分對象生命周期很短暫,少部分對象生命周期很長,有的甚至與應用程序以及Java虛擬機的運行周期一樣長。因此,應該對不同生命周期的對象采取不同的收集策略,根據生命周期長短將它們放到不同的區域,并在不同的區域采用不同的收集算法,這就是分代的概念。現在主流的Java虛擬機的垃圾收集器都采用分代收集算法。Java堆區基于分代的概念,分為新生代和老年代,其中新生代再細分為Eden空間、From Survivor空間和To Survivor空間。因為Eden空間中的大多數生命周期很短,所以新生代的空間劃分并不是均分的,HotSpot虛擬機默認Eden空間和兩個Survivor空間的所占的比例為8:1。
根據Java堆區的空間劃分,垃圾收集的類型分為兩種,它們分別如下:
- Minor Collection:新生代垃圾收集。
- Full Collection:對老年代進行收集,又可以稱作Major Collection,Full Collection通常情況下會伴隨至少一次的Minor Collection,它的收集頻率較低,耗時較長。
當執行一次Minor Collection時,Eden空間的存活對象會被復制到To Survivor空間,并且之前經過一次Minor Collection 并在From Survivor空間存活的仍年輕的對象也會復制到To Survivor空間。有兩種情況Eden空間和From Survivor空間存活的對象不會復制到To Survivor空間, 而是晉升到老年代。一種是存活的對象的分代年齡超過-XX:MaxTenuringThreshold(用于控制對象經歷多少次Minor GC 才晉升到老年代)所指定的閾值。另一種是To Survivor空間容量達到閾值。當所有存活的對象被復制到To Survivor空間,或者晉升到老年代,也就意味著Eden空間和From Survivor空間剩下的都是可回收對象。
這個時候GC執行Minor Collection,Eden空間和From Survivor空間都會被清空,新生代存活的對象都存放在To Survivor空間。接下來將From Survivor空間和To Survivor空間互換位置,也就是此前的From Survivor空間成為了現在的To Survivor空間,每次Survivor空間互換都要保證To Survivor空間是空的,這就是復制算法在新生代中的應用。在老年代則會采用標記-壓縮算法或標記-清除算法。