1、GC基本概念java
GC(Garbage Collection)垃圾收集,1960年最先在List中使用。在Java中GC回收的對象是堆空間和永久區,能夠有效避免程序員人爲形成內存泄漏問題。將堆空間和永久區沒有做用的對象進行釋放和回收。程序員
2、GC算法算法
一、引用計數法:jvm
是一種老牌的垃圾回收算法,經過引用計算來回收垃圾,被COM、ActionScript三、Python所使用。ide
引用計數法的實現很簡單,對於一個對象A,只要有任何一個對象引用了A,那麼A的引用計數器就會+1,當引用失效時,引用計數器就會-1。只要對象A的引用計數器的值爲0,那麼對象A 就不可能再被使用。函數
引用計數法存在的問題:性能
1)引用和去引用伴隨着加法,程序運行時隨時都發生着引用和去引用,影響性能;測試
2)很難處理循環引用的問題,以下圖:優化
中間節點未被根節點引用,但通過一次循環後引用計數爲2,仍然不會被清除,實際上應該被清除。this
二、標記-清除法:
標記-清除算法是現代垃圾回收算法的思想基礎。標記-清除算法將垃圾回收分爲兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先經過根節點,標記全部從根節點開始的可達對象。所以未被標記的對象就是未被引用的垃圾對象。而後在清除階段,清除全部未被標記的對象。
灰色對象都是根節點的可達對象,黑色對象未被根節點引用(直接或間接),白色部分爲空閒空間。清理階段會將黑色對象(未被標記)清理掉。
三、標記-壓縮法:
標記-壓縮算法適合存活對象比較多的場合,如老年代。它在標記-清除算法的基礎上作了一些優化。和標記-清除算法同樣,標記-壓縮算法也首先須要從根節點開始,對全部可達對象作一次標記,但以後,它並不只僅簡單的清理未標記的對象,而是將全部存活的對象壓縮到內存的一端。以後清理邊界外全部的對象。以下圖:
四、複製算法
與標記-清除算法相比,複製算法是一種相對高效的回收算法,但不適用於存活對象較多的場合,如老年代。
它主要實現方案是將原有的內存空間分爲兩塊,每次只使用其中的一塊,在垃圾回收時,將正在使用的內存中的存活對象複製到未被使用的內存塊中,以後,清除正在使用的內存塊中全部對象,交換兩個內存塊的角色,完成垃圾回收。
因而可知,複製算法最大的問題是空間浪費。Java中實際應用作了一些優化(分代思想,下面介紹),示例圖以下:
新生代總空間爲15M,但可用空間只有13824K。其中12288K爲eden區空間,主要存放新產生的對象,1536K爲新生代兩塊相同空間(倖存代中from區和to區)中其中一塊。
3、分代思想:
一、依據對象的存活週期進行分類,短命對象歸爲新生代,長命對象歸爲老年代;
二、根據不一樣代的特色,選取合適的GC算法,少許對象存活,適合複製算法;大量對象存活,適合標記清理或者標記壓縮。
4、可觸及性:
全部的算法須要可以識別一個垃圾對象,所以引入了可觸及性的概念。
一、可觸及的:從根節點能夠觸及到的對象;
二、可復活的:一旦全部引用都被釋放,就是可復活狀態;由於有可能在finalize()方法中可能復活該對象;
三、不可觸及的:在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()方法。
5、根對象
什麼是根對象呢?主要有如下三類:
一、棧中引用的對象;
二、方法區靜態成員或者常量引用的對象(全局變量);
三、JNI方法棧中引用的對象。
6、STOP-THE-WORLD
STOP-THE-WORLD是Java中一種全局停頓的現象,此時全部Java代碼中止運行,native方法能夠運行可是沒法與jvm發生交互,發生這種狀況多半是因爲GC引發的。另外Dump檢查、死鎖檢查、堆Dump也有可能引發。
一、GC爲何會引發全局停頓?
類比在聚會時打掃衛生,聚會時很亂,又會產生新的垃圾,房間永遠不會被打掃乾淨,只有暫停一下聚會纔會將房間打掃乾淨。
二、全局停頓的危害
長時間中止服務,沒有響應;對於HA系統可能會引發主備切換。
三、寫一個測試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條記錄,後來產生了全局停頓。
GC發生的時間與全局停頓的時間是吻合的。