JVM 垃圾回收機制

1. GC的概念

Garbage Collection 垃圾收集 java

Java中,GC的對象是堆空間和永久區(若是對Java內存區域不太瞭解,請查看Java內存區域) 算法

2. GC算法

2.1 引用計數法

老牌垃圾回收算法
經過引用計算來回收垃圾 ide

使用者: 性能

  • COM
  • ActionScript3
  • Python

引用計數器的實現很簡單,對於一個對象A,只要有任何一個對象引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1。只要對象A的引用計數器的值爲0,則對象A就不可能再被使用。 優化

因此當一個對象的引用數量爲0,就意味着沒有人引用了這個對象,就能夠將這個對象GC掉。 this

引用計數法的問題 spa

  • 引用和去引用伴隨加法和減法,影響性能
  • 很難處理循環引用

從上圖能夠看出,右邊的3個對象,最後的引用計數都是1,可是都不被根對象所引用,三個垃圾對象循環引用,致使都沒法被回收。 .net

這裏要注意的是,引用計數法在Java中沒有采用。 線程

2.2 標記-清除

標記-清除算法是現代垃圾回收算法的思想基礎。標記-清除算法將垃圾回收分爲兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先經過根節點,標記全部從根節點開始的可達對象。所以,未被標記的對象就是未被引用的垃圾對象。而後,在清除階段,清除全部未被標記的對象。

2.3 標記-壓縮

標記-壓縮算法適合用於存活對象較多的場合,如老年代。它在標記-清除算法的基礎上作了一些優化。和標記-清除算法同樣,標記-壓縮算法也首先須要從根節點開始,對全部可達對象作一次標記。但以後,它並不簡單的清理未標記的對象,而是將全部的存活對象壓縮到內存的一端。以後,清理邊界外全部的空間。


2.4 複製算法

  • 與標記-清除算法相比,複製算法是一種相對高效的回收方法
  • 不適用於存活對象較多的場合 如老年代
  • 將原有的內存空間分爲兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內存中的存活對象複製到未使用的內存塊中,以後,清除正在使用的內存塊中的全部對象,交換兩個內存的角色,完成垃圾回收


複製算法的問題空間浪費,只能使用一半空間 code

經過整合標記清理思想來使得空間不怎麼浪費


上圖中老年代與複製算法關係不大,老年代通常是擔保空間。
最上面那塊大的區域,當作對象產生的空間。

中間那塊就是複製算法的核心區域,兩塊區域相同大小的空間。

具體步驟是:

1. 在最上面那塊大的區域產生新對象。

2. 大對象不太適合在複製空間,由於複製空間的容量是有限的,因此須要一個大的空間作擔保,因此讓老年代作擔保。這樣產生的大對象直接進入老年代。

3. 每一次GC,對象的年齡就會+1,一個對象在幾回GC後仍然沒有被回收,則這個對象就是一個老年對象。老年對象是一個長期被引用的對象,老年對象將被放入老年代。

4. 步驟1中產生的小對象,將進入到複製空間。原先複製空間中的新對象也將被複制到另外一塊複製空間

5. 清空垃圾對象

-XX:+PrintGCDetails的輸出


根據-XX:+PrintGCDetails的輸出,咱們能夠看到

一個堆分爲new generation(新生代) , tenured generation(老年代)和compacting perm gen。

new generation分爲eden space,from space(有些地方稱爲s0和s1,表示倖存代) , to space。

eden space就是上面那種圖中,對象產生的地方。

from space和to space是兩塊大小同樣的區域,是上圖中的複製空間。

new generation的可用總空間就是eden space+一塊複製空間(另外一塊不算),可是根據new generation的地址訪問能夠算出是eden space + 兩塊複製空間區域,因此複製算法浪費了一部分空間。

2.5 分代思想

依據對象的存活週期進行分類,短命對象歸爲新生代,長命對象歸爲老年代。
根據不一樣代的特色,選取合適的收集算法

  • 少許對象存活,適合複製算法
  • 大量對象存活,適合標記清理或者標記壓縮

進入老年代的對象有兩種狀況:

1. 新生代空間不夠,老年代作擔保存放一些大對象

2. 某些對象屢次GC後仍然存在,進入老年代。

老年代的大多數對象都是第2種狀況,因此老年代的對象的生命週期比較長,GC的發生也比較少,會有大量對象存活,因此不用複製算法,而改成標記清理或者標記壓縮。

全部的算法,須要可以識別一個垃圾對象,所以須要給出一個可觸及性的定義

3. 可觸及性

可觸及的

  • 從根節點能夠觸及到這個對象
可復活的
  • 一旦全部引用被釋放,就是可復活狀態
  • 由於在finalize()中可能復活該對象
不可觸及的
  • 在finalize()後,可能會進入不可觸及狀態
  • 不可觸及的對象不可能復活
  • 能夠回收
下面舉個例子來講明可復活這個狀態:
public class CanReliveObj
{
	public static CanReliveObj obj;

	@Override
	protected void finalize() throws Throwable
	{
		super.finalize();
		System.out.println("CanReliveObj finalize called");
		obj = this;
	}

	@Override
	public String toString()
	{
		return "I am CanReliveObj";
	}

	public static void main(String[] args) throws InterruptedException
	{
		obj = new CanReliveObj();
		obj = null; // 可復活
		System.gc();
		Thread.sleep(1000);
		if (obj == null)
		{
			System.out.println("obj 是 null");
		}
		else
		{
			System.out.println("obj 可用");
		}
		System.out.println("第二次gc");
		obj = null; // 不可復活
		System.gc();
		Thread.sleep(1000);
		if (obj == null)
		{
			System.out.println("obj 是 null");
		}
		else
		{
			System.out.println("obj 可用");
		}
	}

}

輸出:

CanReliveObj finalize called
obj 可用
第二次gc
obj 是 null
通常咱們認爲,對象賦值null後,對象就能夠被GC了,在上述實例中,在finalize中,又將obj=this,使對象復活。由於finalize只能調用一次,因此第二次GC時,obj被回收。

所以對於finalize會有這樣的建議:

  • 經驗:避免使用finalize(),操做不慎可能致使錯誤。
  • finalize優先級低,什麼時候被調用(在GC時被調用,什麼時候發生GC不肯定) 不肯定
  • 可使用try-catch-finally來替代它
另外在以前,咱們一直在提到從根出發,那麼根是指哪些對象呢?
  • 棧中引用的對象
  • 方法區中靜態成員或者常量引用的對象(全局對象)
  • JNI方法棧中引用對象

4. Stop-The-World

Stop-The-World是Java中一種全局暫停的現象。

全局停頓,全部Java代碼中止,native代碼能夠執行,但不能和JVM交互

多半因爲GC引發,固然Dump線程、死鎖檢查、堆Dump都有可能引發Stop-The-World

GC時爲何會有全局停頓?

類比在聚會時打掃房間,聚會時很亂,又有新的垃圾產生,房間永遠打掃不乾淨,只有讓你們中止活動了,才能將房間打掃乾淨。

危害

長時間服務中止,沒有響應
遇到HA系統,可能引發主備切換,嚴重危害生產環境。

新生代的GC(Minor GC),停頓時間比較短

老年代的GC(Full GC),停頓時間可能比較長

相關文章
相關標籤/搜索