來源:Java聯盟

Java技術體系中所提倡的自動內存管理最終可以歸結爲自動化地解決了兩個問題:給對象分配內存以及回收分配給對象的內存,而且這兩個問題針對的內存區域就是Java內存模型中的堆區。

關於對象分配內存問題,如何劃分可用空間及其涉及到的線程安全問題,本文將結合垃圾回收策略進一步給出內存分配規則。

垃圾回收機制的引入可以有效的防止內存泄露、保證內存的有效使用,也大大解放了Java程序員的雙手,使得他們在編寫程序的時候不再需要考慮內存管理。

那麼,今天我們就深入講解關於Java虛擬機的知識,GC都有哪幾種算法,以及JVM都有那些垃圾回收器,它們的工作原理。

GC概述

垃圾收集 Garbage Collection 通常被稱爲“GC”,它誕生於1960年 MIT 的 Lisp 語言,經過半個多世紀,目前已經十分成熟了。

jvm 中,程序計數器、虛擬機棧、本地方法棧都是隨線程而生隨線程而滅,棧幀隨着方法的進入和退出做入棧和出棧操作,實現了自動的內存清理,因此,我們的內存垃圾回收主要集中於 java 堆和方法區中,在程序運行期間,這部分內存的分配和使用都是動態的.

爲什麼要進行垃圾回收?

我們都知道java虛擬機的內存模型一共包括三個部分:堆 ( Java代碼可及的 Java堆 和 JVM自身使用的方法區)、棧 ( 服務Java方法的虛擬機棧 和 服務Native方法的本地方法棧 ) 和 保證程序在多線程環境下能夠連續執行的程序計數器。

特別地,我們當時就提到Java堆是進行垃圾回收的主要區域,故其也被稱爲GC堆;而方法區也有一個不太嚴謹的表述,就是永久代。總的來說,堆 (包括Java堆 和 方法區)是 垃圾回收的主要對象,特別是Java堆。

關於對象分配內存問題,筆者的博文《JVM 內存模型概述》已經闡述了如何劃分可用空間及其涉及到的線程安全問題,本文將結合垃圾回收策略進一步給出內存分配規則。

另外,我們知道垃圾回收機制是Java語言一個顯著的特點,其可以有效的防止內存泄露、保證內存的有效使用,從而使得Java程序員在編寫程序的時候不再需要考慮內存管理問題。

關於Java虛擬機的GC算法、垃圾收集器,你能說出幾種?

Java 垃圾回收機制要考慮的問題很複雜,本文闡述了其三個核心問題,包括:

那些內存需要回收?(對象是否可以被回收的兩種經典算法: 引用計數法 和 可達性分析算法)

什麼時候回收?(堆的新生代、老年代、永久代的垃圾回收時機,MinorGC 和 FullGC)

如何回收?(三種經典垃圾回收算法(標記清除算法、複製算法、標記整理算法)及分代收集算法 和 七種垃圾收集器)

如何確定一個對象是否可以被回收?

判斷對象是否存活一般有兩種方式:

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

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

在Java語言中,GC Roots包括:

虛擬機棧中引用的對象。

方法區中類靜態屬性實體引用的對象。

方法區中常量引用的對象。

本地方法棧中JNI引用的對象。

垃圾收集法

01

標記-清除算法

這個應該很容易理解了,標記需要清除的對象,然後直接清理掉,這是最基礎的算法了,後面的收集法也都是基於這種思路的

但是它有它的缺點:標記跟清理的效率都很低,標記清理使得內存不規整,如果出現需要分配大內存的情況下,連續的內存不足,那就要觸發垃圾收集器回收,而過多的垃圾收集,造成資源浪費,耗費時間,次數多了,影響程序運行流暢。

關於Java虛擬機的GC算法、垃圾收集器,你能說出幾種?

02

複製算法

由於前一種內存碎片化的問題,所以帶來了第二種方法,複製發,依然很容易理解,將內存分成相同的兩塊,先集中在一塊內存上分配。

當垃圾收集器回收以後,將存活的對象複製到另一塊內存上保存,這樣可以保證內存的有序性,實現簡單,運行高效。

但是這種方法,需要將可用內存大小犧牲一半,內存的容量減小,也會增加垃圾收集器工作次數。

關於Java虛擬機的GC算法、垃圾收集器,你能說出幾種?

03

標記-整理

複製收集算法在對象存活率較高時就要進行較多的複製操作,效率將會變低。 更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,老年代對象存活的比較多,採用複製算法,效率不好。

針對存活內存塊上存活對象多的情況下,提出了標記-整理法,標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。

關於Java虛擬機的GC算法、垃圾收集器,你能說出幾種?

04

分代回收

當前商業虛擬機的垃圾收集都採用“分代收集”(Generational Collection)算法,這種算法並沒有什麼新的思想,只是根據對象存活週期的不同將內存劃分爲幾塊。

一般是把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。

在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。

而老年代中因爲對象存活率高、 沒有額外空間對它進行分配擔保,就必須使用“標記—清理”或者“標記—整理”算法來進行回收。

垃圾收集器

如果說垃圾收集算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現。

下圖展示了7種作用於不同分代的收集器,其中用於回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS

還有用於回收整個Java堆的G1收集器。不同收集器之間的連線表示它們可以搭配使用。

關於Java虛擬機的GC算法、垃圾收集器,你能說出幾種?

Serial收集器(複製算法): 新生代單線程收集器,標記和清理都是單線程,優點是簡單高效;

Serial Old收集器 (標記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;

ParNew收集器 (複製算法): 新生代收並行集器,實際上是Serial收集器的多線程版本,在多核CPU環境下有着比Serial更好的表現;

Parallel Scavenge收集器 (複製算法): 新生代並行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用戶線程時間/(用戶線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,儘快完成程序的運算任務,適合後臺應用等對交互相應要求不高的場景;

Parallel Old收集器 (標記-整理算法): 老年代並行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;

CMS(Concurrent Mark Sweep)收集器(標記-清除算法): 老年代並行收集器,以獲取最短回收停頓時間爲目標的收集器,具有高併發、低停頓的特點,追求最短GC回收停頓時間。

G1(Garbage First)收集器 (標記-整理算法): Java堆並行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基於“標記-整理”算法實現,也就是說不會產生內存碎片。

此外,G1收集器不同於之前的收集器的一個重要特點是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限於新生代或老年代。

相關文章