天天看點

深入探究jvm之GC的算法及種類

一、GC基本概念

  GC(Garbage Collection)垃圾收集,1960年最早在List中使用。在Java中GC回收的對象是堆空間和永久區,可以有效避免程式員人為造成記憶體洩漏問題。将堆空間和永久區沒有作用的對象進行釋放和回收。

二、GC算法

1、引用計數法:

  是一種老牌的垃圾回收算法,通過引用計算來回收垃圾,被COM、ActionScript3、Python所使用。

  引用計數法的實作很簡單,對于一個對象A,隻要有任何一個對象引用了A,那麼A的引用計數器就會+1,當引用失效時,引用計數器就會-1。隻要對象A的引用計數器的值為0,那麼對象A 就不可能再被使用。

  

深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類

  引用計數法存在的問題:

  1)引用和去引用伴随着加法,程式運作時随時都發生着引用和去引用,影響性能;

  2)很難處理循環引用的問題,如下圖:

深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類

   中間節點未被根節點引用,但經過一次循環後引用計數為2,仍然不會被清除,實際上應該被清除。

2、标記-清除法:

  标記-清除算法是現代垃圾回收算法的思想基礎。标記-清除算法将垃圾回收分為兩個階段:标記階段和清除階段。一種可行的實作是,在标記階段,首先通過根節點,标記所有從根節點開始的可達對象。是以未被标記的對象就是未被引用的垃圾對象。然後在清除階段,清除所有未被标記的對象。

深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類

   灰色對象都是根節點的可達對象,黑色對象未被根節點引用(直接或間接),白色部分為空閑空間。清理階段會将黑色對象(未被标記)清理掉。

3、标記-壓縮法:

  标記-壓縮算法适合存活對象比較多的場合,如老年代。它在标記-清除算法的基礎上做了一些優化。和标記-清除算法一樣,标記-壓縮算法也首先需要從根節點開始,對所有可達對象做一次标記,但之後,它并不僅僅簡單的清理未标記的對象,而是将所有存活的對象壓縮到記憶體的一端。之後清理邊界外所有的對象。如下圖:

深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類

4、複制算法

  與标記-清除算法相比,複制算法是一種相對高效的回收算法,但不适用于存活對象較多的場合,如老年代。

  它主要實作方案是将原有的記憶體空間分為兩塊,每次隻使用其中的一塊,在垃圾回收時,将正在使用的記憶體中的存活對象複制到未被使用的記憶體塊中,之後,清除正在使用的記憶體塊中所有對象,交換兩個記憶體塊的角色,完成垃圾回收。

  

深入探究jvm之GC的算法及種類

  由此可見,複制算法最大的問題是空間浪費。Java中實際應用做了一些優化(分代思想,下面介紹),示例圖如下:

深入探究jvm之GC的算法及種類

  新生代總空間為15M,但可用空間隻有13824K。其中12288K為eden區空間,主要存放新産生的對象,1536K為新生代兩塊相同空間(幸存代中from區和to區)中其中一塊。

深入探究jvm之GC的算法及種類

三、分代思想:

  1、依據對象的存活周期進行分類,短命對象歸為新生代,長命對象歸為老年代;

  2、根據不同代的特點,選取合适的GC算法,少量對象存活,适合複制算法;大量對象存活,适合标記清理或者标記壓縮。

 四、可觸及性:

  所有的算法需要能夠識别一個垃圾對象,是以引入了可觸及性的概念。

  1、可觸及的:從根節點可以觸及到的對象;

  2、可複活的:一旦所有引用都被釋放,就是可複活狀态;因為有可能在finalize()方法中可能複活該對象;

  3、不可觸及的:在finalize()方法後,可能會進入不可觸及狀态,不可觸及的對象不可能被複活,此時可以被GC回收。

  對象狀态轉換如下:

  1)首先定義一個可複活對象,在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";
	}
}
           

  2)主函數方法如下:

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 可用");
  }
}
           

  3)運作結果如下:

CanReliveObj finalize called
obj 可用
第二次gc
obj 是 null
           

  注意,如果在調用finalize()方法後忘記釋放記憶體,那麼可複活對象就會一直存在于堆記憶體中,很容易造成記憶體溢出,是以是有風險的。在編碼的時候要注意以下幾點:

  1)避免使用finalize()方法,操作不慎可能導緻錯誤;

  2)優先級很低,因為我們不知道也無法明确GC什麼時候發生,使用finalize()方法反而增加了程式的不确定性;

  3)可以使用try-catch-finally來代替finalize()方法。

五、根對象

  什麼是根對象呢?主要有以下三類:

  1、棧中引用的對象;

  2、方法區靜态成員或者常量引用的對象(全局變量);

  3、JNI方法棧中引用的對象。

六、STOP-THE-WORLD

  STOP-THE-WORLD是Java中一種全局停頓的現象,此時所有Java代碼停止運作,native方法可以運作但是無法與jvm發生互動,發生這種情況多半是由于GC引起的。另外Dump檢查、死鎖檢查、堆Dump也有可能引起。

  1、GC為什麼會引起全局停頓?

  類比在聚會時打掃衛生,聚會時很亂,又會産生新的垃圾,房間永遠不會被打掃幹淨,隻有暫停一下聚會才會将房間打掃幹淨。

  2、全局停頓的危害

  長時間停止服務,沒有響應;對于HA系統可能會引起主備切換。

  3、寫一個測試demo驗證STOP-THE-WORLD的存在

  1)聲明一個線程,每過1s列印10條記錄;

public static class PrintThread extends Thread{
	public static final long starttime=System.currentTimeMillis();
	@Override
	public void run(){
		try{
			while(true){
				long t=System.currentTimeMillis()-starttime;
				System.out.println("time:"+t);
				Thread.sleep(100);
			}
		}catch(Exception e){
			
		}
	}
}
           

  2)聲明另外一個線程消耗資源,用來觸發GC(不斷的占用記憶體,不斷的釋放變量來觸發GC)

public static class MyThread extends Thread{
	HashMap<Long,byte[]> map=new HashMap<Long,byte[]>();
	@Override
	public void run(){
		try{
			while(true){
				if(map.size()*512/1024/1024>=450){
					System.out.println(“=====準備清理=====:"+map.size());
					map.clear();
				}
				
				for(int i=0;i<1024;i++){
					map.put(System.nanoTime(), new byte[512]);
				}
				Thread.sleep(1);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
           

  3)啟動JVM的參數為512m堆空間、串行回收器

-Xmx512M -Xms512M -XX:+UseSerialGC -Xloggc:gc.log -XX:+PrintGCDetails  -Xmn1m -XX:PretenureSizeThreshold=50 -XX:MaxTenuringThreshold=1
           

  4)觀察控制台輸出和GC日志,開始是1s列印10條記錄,後來産生了全局停頓。

深入探究jvm之GC的算法及種類
深入探究jvm之GC的算法及種類

  GC發生的時間與全局停頓的時間是吻合的。

  

  

  

  

  

  

轉載于:https://www.cnblogs.com/liuyk-code/p/10289949.html