垃圾收集主要是針對堆和方法區進行。算法
程序計數器、虛擬機棧和本地方法棧這三個區域屬於線程私有的,只存在於線程的生命週期內,線程結束以後也會消失,所以不須要對這三個區域進行垃圾回收。多線程
判斷一個對象是否可被回收併發
給對象添加一個引用計數器,當對象增長一個引用時計數器加 1,引用失效時計數器減 1。引用計數爲 0 的對象可被回收。框架
兩個對象出現循環引用的狀況下,此時引用計數器永遠不爲 0,致使沒法對它們進行回收。函數
正由於循環引用的存在,所以 Java 虛擬機不使用引用計數算法。性能
public class ReferenceCountingGC { public Object instance = null; public static void main(String[] args) { ReferenceCountingGC objectA = new ReferenceCountingGC(); ReferenceCountingGC objectB = new ReferenceCountingGC(); objectA.instance = objectB; objectB.instance = objectA; } }
經過 GC Roots 做爲起始點進行搜索,可以到達到的對象都是存活的,不可達的對象可被回收。測試
Java 虛擬機使用該算法來判斷對象是否可被回收,在 Java 中 GC Roots 通常包含如下內容:spa
由於方法區主要存放永久代對象,而永久代對象的回收率比新生代低不少,所以在方法區上進行回收性價比不高。線程
主要是對常量池的回收和對類的卸載。翻譯
在大量使用反射、動態代理、CGLib 等 ByteCode 框架、 動態生成 JSP 以及 OSGi 這類頻繁自定義 ClassLoader 的場景都須要虛擬機具有類卸載功能,以保證不會出現內存溢出。
類的卸載條件不少,須要知足如下三個條件,而且知足了也不必定會被卸載:
能夠經過 -Xnoclassgc 參數來控制是否對類進行卸載。
finalize() 相似 C++ 的析構函數,用來作關閉外部資源等工做。可是 try-finally 等方式能夠作的更好, 而且該方法運行代價高昂,不肯定性大,沒法保證各個對象的調用順序,所以最好不要使用。
當一個對象可被回收時,若是須要執行該對象的 finalize() 方法, 那麼就有可能在該方法中讓對象從新被引用,從而實現自救。 自救只能進行一次,若是回收的對象以前調用了 finalize() 方法自救,後面回收時不會調用 finalize() 方法。
不管是經過引用計算算法判斷對象的引用數量,仍是經過可達性分析算法判斷對象是否可達, 斷定對象是否可被回收都與引用有關。
Java 提供了四種強度不一樣的引用類型。
被強引用關聯的對象不會被回收。
使用 new 一個新對象的方式來建立強引用。
Object obj = new Object();
被軟引用關聯的對象只有在內存不夠的狀況下才會被回收。
使用 SoftReference 類來建立軟引用。
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; // 使對象只被軟引用關聯
被弱引用關聯的對象必定會被回收,也就是說它只能存活到下一次垃圾回收發生以前。
使用 WeakReference 類來實現弱引用。
Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj); obj = null;
又稱爲幽靈引用或者幻影引用。一個對象是否有虛引用的存在, 徹底不會對其生存時間構成影響,也沒法經過虛引用取得一個對象。
爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被回收時收到一個系統通知。
使用 PhantomReference 來實現虛引用。
Object obj = new Object(); PhantomReference<Object> pf = new PhantomReference<Object>(obj); obj = null;
將存活的對象進行標記,而後清理掉未被標記的對象。
不足:
讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
將內存劃分爲大小相等的兩塊,每次只使用其中一塊,當這一塊內存用完了就將還存活的對象複製到另外一塊上面,而後再把使用過的內存空間進行一次清理。
主要不足是隻使用了內存的一半。
如今的商業虛擬機都採用這種收集算法來回收新生代,可是並非將新生代劃分爲大小相等的兩塊,而是分爲一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 空間和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活着的對象一次性複製到另外一塊 Survivor 空間上,最後清理 Eden 和使用過的那一塊 Survivor。
HotSpot 虛擬機的 Eden 和 Survivor 的大小比例默認爲 8:1,保證了內存的利用率達到 90%。若是每次回收有多於 10% 的對象存活,那麼一塊 Survivor 空間就不夠用了,此時須要依賴於老年代進行分配擔保,也就是借用老年代的空間存儲放不下的對象。
如今的商業虛擬機採用分代收集算法,它根據對象存活週期將內存劃分爲幾塊,不一樣塊採用適當的收集算法。
通常將堆分爲新生代和老年代。
以上是 HotSpot 虛擬機中的 7 個垃圾收集器,連線表示垃圾收集器能夠配合使用。
Serial 翻譯爲串行,也就是說它以串行的方式執行。
它是單線程的收集器,只會使用一個線程進行垃圾收集工做。
它的優勢是簡單高效,對於單個 CPU 環境來講,因爲沒有線程交互的開銷,所以擁有最高的單線程收集效率。
它是 Client 模式下的默認新生代收集器,由於在該應用場景下,分配給虛擬機管理的內存通常來講不會很大。Serial 收集器收集幾十兆甚至一兩百兆的新生代停頓時間能夠控制在一百多毫秒之內,只要不是太頻繁,這點停頓是能夠接受的。
它是 Serial 收集器的多線程版本。
是 Server 模式下的虛擬機首選新生代收集器,除了性能緣由外,主要是由於除了 Serial 收集器,只有它能與 CMS 收集器配合工做。
默認開啓的線程數量與 CPU 數量相同,可使用 -XX:ParallelGCThreads 參數來設置線程數。
與 ParNew 同樣是多線程收集器。
其它收集器關注點是儘量縮短垃圾收集時用戶線程的停頓時間,而它的目標是達到一個可控制的吞吐量,它被稱爲「吞吐量優先」收集器。這裏的吞吐量指 CPU 用於運行用戶代碼的時間佔總時間的比值。
停頓時間越短就越適合須要與用戶交互的程序,良好的響應速度能提高用戶體驗。而高吞吐量則能夠高效率地利用 CPU 時間,儘快完成程序的運算任務,適合在後臺運算而不須要太多交互的任務。
縮短停頓時間是以犧牲吞吐量和新生代空間來換取的:新生代空間變小,垃圾回收變得頻繁,致使吞吐量降低。
能夠經過一個開關參數打開 GC 自適應的調節策略(GC Ergonomics), 就不須要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 區的比例、晉升老年代對象年齡等細節參數了。 虛擬機會根據當前系統的運行狀況收集性能監控信息, 動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量。 自適應調節策略是 Parallel Scavenge 收集器和 ParNew 收集器的一個重要區別。
是 Serial 收集器的老年代版本,也是給 Client 模式下的虛擬機使用,採用標記-整理算法。若是用在 Server 模式下,它有兩大用途:
是 Parallel Scavenge 收集器的老年代版本,採用標記-整理算法。
在注重吞吐量以及 CPU 資源敏感的場合,均可以優先考慮 Parallel Scavenge 加 Parallel Old 收集器。
CMS(Concurrent Mark Sweep),Mark Sweep 指的是標記 - 清除算法。
分爲如下四個流程:
在整個過程當中耗時最長的併發標記和併發清除過程當中,收集器線程均可以與用戶線程一塊兒工做,不須要進行停頓,具備併發收集、低停頓的優勢。
具備如下缺點:
CMS 已經在 JDK 9 中被標記爲廢棄( deprecated )。
G1(Garbage-First),它是一款面向服務端應用的垃圾收集器,在多 CPU 和大內存的場景下有很好的性能。HotSpot 開發團隊賦予它的使命是將來能夠替換掉 CMS 收集器。
堆被分爲新生代和老年代,其它收集器進行收集的範圍都是整個新生代或者老年代,而 G1 能夠直接對新生代和老年代一塊兒回收。
G1 把堆劃分紅多個大小相等的獨立區域(Region),Region的大小是一致的,數值是在1M到32M字節之間的一個2的冪值數,JVM會盡可能劃分2048個左右、同等大小的Region,新生代和老年代再也不物理隔離。
經過引入 Region 的概念,從而將原來的一整塊內存空間劃分紅多個的小空間,使得每一個小空間能夠單獨進行垃圾回收。這種劃分方法帶來了很大的靈活性,使得可預測的停頓時間模型成爲可能。經過記錄每一個 Region 垃圾回收時間以及回收所得到的空間(這兩個值是經過過去回收的經驗得到),並維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的 Region。
每一個 Region 都有一個 Remembered Set,用來記錄該 Region 對象的引用對象所在的 Region。經過使用 Remembered Set,在作可達性分析的時候就能夠避免全堆掃描。
若是不計算維護 Remembered Set 的操做,G1 收集器的運做大體可劃分爲如下幾個步驟:
從 GC 算法的角度, G1 選擇的是複合算法,能夠簡化理解爲:
具有以下特色:
目前尚處於開發中的 JDK 11, JDK 又增長了兩種全新的 GC 方式,分別是:
- Epsilon GC,簡單說就是個不作垃圾收集的GC,彷佛有點奇怪,有的狀況下,例如在進行性能測試的時候,可能須要明確判斷GC自己產生了多大的開銷,這就是其典型應用場景。
- ZGC,這是Oracle開源出來的一個超級GC實現,具有使人驚訝的擴展能力,好比支持T bytes級別的堆大小,而且保證絕大部分狀況下,延遲都不會超過10 ms。雖然目前還處於實驗階段,僅支持 Linux 64 位的平臺,但其已經表現出的能力和潛力都很是使人期待。