Java垃圾回收機制(Gabage Collection)是Java與C/C++之間的巨大差距所在,但垃圾回收不是Java的專利,任何編程語言都可以考慮和設計垃圾回收。
垃圾回收考慮三件事情: 1,哪些內存需要回收? 2,什么時候回收? 3,怎么回收?**
Java垃圾回收自動化地幫我們清理不再使用的內存,既然是“自動化”,為什么還要了解? 答:當需要排查內存泄漏和內存溢出問題時,當垃圾回收機制成為更高并發性的瓶頸時,需要人工對這些環節進行監控和調節。
Java內存分成若干區塊,分別是:程序計數器(PRogram Counter Register),虛擬機棧(VM Stack),本地方法棧(Native Method Stack),Java堆(Java Heap)和方法區(Method Area)。
其中,程序計數器,虛擬機棧和本地方法棧是與線程同生死共命運的,因此它們的管理邏輯是比較清晰的。而堆和方法區則不一樣,一個接口的不同實現類所需的內存不一樣,一個方法的不同分支所需的內存也不一樣,這些內存的分配需要在程序運行過程中動態管理。分配與回收都是動態的,所謂Java垃圾回收,管理的就是這部分呢。
回到之前我們說過垃圾回收需要思考三個問題:1,哪些內存需要回收? 2,什么時候回收? 3,怎么回收? 這些問題需要一一解答。
或者換個說法——如何判斷一個資源是否不再被使用(“死了”)。
對于Java Heap中的對象回收有兩種方案:
1,簡單而清晰的第一種方案——引用計數算法
給對象添加一個引用計數器,當有一個地方引用了這個對象,計數器就加一,當這個引用失效,計數器減一,當計數器值減為0,說明對象“已死”,可以回收。
引用計數法簡單明了,易于實現,但存在問題,比如,無法解決循環引用情況:
如果對象A和對象B相互引用,即A中有一個屬性引用B,B中有一個屬性引用A。當A和B都不在被外部所使用時,他們之間的引用仍然存在,計數器值不為0,所以不會被回收。
因此,引用計數器算法有其局限性。
2,根搜索法
通過一系列被稱為“GC Roots”的對象作為初始點,逐步向下游進行搜索,搜索做走過的路徑稱為“引用鏈”,當一個對象不在任何一條引用鏈上的時間后,說明它已經“死亡”。
在Java語言中,可作為“GC Roots”的對象如下:
1,虛擬機中的引用對象2,方法區中的類靜態屬性引用的對象3,方法區中常量引用的對象4,本地方法棧JNI引用的對象可以發現無論是通過引用計數器還是根搜索法,垃圾標定都與引用相關。
因此,為了賦予此更大的彈性空間,引用也有了分類之說。
引用的分類根本上是按照:當被引用的對象和GC遭遇,引用的“強硬”程度區分的。“強硬”程度由高到低,分為“強引用”,“軟引用”,“弱引用”,“弱引用”。
強引用:只要強引用還在,GC永遠不會回收。
軟引用:還有用的引用,內存空間充裕不回收,如果回收發現仍然會超出內存,再把軟引用部分回收。
弱引用:沒用的引用,GC二話不說,回收!
虛引用(幽靈引用):一個對象是否存在虛引用,對其生存時間不構成影響,無法通過虛引用獲得對象實例,只是在垃圾回收時,收到一個系統通知。換句話說,虛引用是輔助垃圾回收機制的設計。
這有點像商販和城管的關系。
強引用就是證照齊全,固定門點,城管是不會管的。
軟引用是也有證照,但是流動攤販。城管可以不管你,但如果上頭來檢查,群眾有舉報,那不好意思,城管要遣走你。
弱引用就是不法小販,遇到就絕不姑息。
虛引用就是那小販已經跑了,剩點家什,城管收走,在賬上記上一筆,收繳XXX一件,就完了。
實際實現細節不在這里深入,不保證比喻的準確,在Oracle官方博客有文章專門討論。
回收逃逸
在一個對象被標記為垃圾等待回收過程中,有一次逃脫的機會,就是finalize()方法,當GC要標記一個對象需要回收時,首先進行一次篩選,篩選對象是否覆蓋了finalize()方法或者JVM已經執行過了finalize()方法。finalize()方法是Object類下定義的方法,可以被覆蓋。如果finalize()被重寫并且沒有被執行,就有可能執行。如果在重寫的finalize()方法中給對象了一個新的引用,對象可以逃過一劫。但是如果第二次遇到GC,即finalize()方法已經執行過了,就不再會執行,直接回收。下面例子說明了這個問題(摘自《深入理解Java虛擬機》):
/** * 演示對象因為被GC而調用finalize自我拯救 * 自我拯救只能拯救一次,系統對一個對象的finalize()方法最多只會調用一次 * */public class FinalizeEscapeGC { public static FinalizeEscapeGC SAVE_HOOK=null; public void isAlive(){ System.out.println("I am still alive"); } @Override public void finalize() throws Throwable{ super.finalize(); System.out.println("Finalize method executes"); FinalizeEscapeGC.SAVE_HOOK=this;//重新建立引用,逃過一劫 } public static void main(String[] args) throws InterruptedException { SAVE_HOOK=new FinalizeEscapeGC(); //第一次垃圾回收,可以逃脫 SAVE_HOOK=null; System.gc(); //Finalizer優先級較低,暫停等待它 Thread.sleep(500); if(SAVE_HOOK!=null){ SAVE_HOOK.isAlive(); }else{ System.out.println("No, I am dead :("); } //第二次垃圾回收,逃脫失敗 SAVE_HOOK=null; System.gc(); //Finalizer優先級較低,暫停等待它 Thread.sleep(500); if(SAVE_HOOK!=null){ SAVE_HOOK.isAlive(); }else{ System.out.println("No, I am dead :("); } }}輸出
Finalize method executesI am still aliveNo, I am dead :(但,書上說,finalize()是一個很不推薦的方法了。
以上都是關于Java Heap中對象的,而對于方法區,略有區別:
方法區中需要回收的東西較比堆里少的多,回收的效率是比較低的,即每次回收仍然有大比率的對象存活。方法區主要回收兩部分對象:廢棄常量和無用類。回收廢棄常量與回收堆中對象類似,根據引用來確定。無用類的判定需要滿足以下條件:
1,該類的所有實例都已經被回收2,加載該類的ClassLoader已經被回收3,該類對應的java.util.Class對象沒有地方在引用,無法在任何地方通過反射訪問此類。上面回答了“哪些內存需要回收?”的問題,接下來要解釋“怎么回收的問題?”的理論。至于具體“怎么回收”以及“什么時候回收”是由具體虛擬機實現,運行參數設置和程序員編寫程序決定的。
垃圾收集算法?
我發現在我們日常整理物品,清理自己算磁盤的時候,就在實踐著一些垃圾回收的理論。只不過我們沒有將其理論化為具體的概念。了解了垃圾收集的具體算法理論,會發現它們就是我們整理東西時可能潛移默化用到的思路
垃圾收集算法有三個:
1,標記-清除算法 2,標記-復制算法 3,標記-整理算法
1,標記-清除算法
根據上面講的垃圾識別理論,標定完垃圾后,將它們刪除掉,就是最基礎的標記-清除算法。
優點:簡潔,高效 缺點:1,清除之后的內存區間可能變成了“千瘡百孔”的內存碎片,如果要存儲大的對象,可能找不到大的連續內存存儲它。2,標記和清除的效率低。
2,標記-復制算法
根據垃圾標識方法,找到了還“生存”的對象,將它們復制到一塊完整的新內存連續空間中。
優點:1,效率高,復制之后,舊的整塊內存可以整體執行刪除操作。2,回收后的內存空間連續 缺點:1,需要將內存分成兩塊進行相互復制切換,損失有效內存。2,當對象“生還率”高,復制操作開銷大。
實際上,新生代的對象往往“朝生夕死”,因此不需要1:1分割內存,而是分割成一大塊Eden區,和兩小塊Survivor區。每次程序運行時使用Eden區和一個Survivor區。垃圾回收時,將這兩塊的生還對象復制到另一塊Survivor區上。比如Eden區和兩個Survivor區的大小比例是8:1:1,那么程序運行的內存可使用率是90%。但是,存在可能,生還對象超過了Survivor區大小,因此需要另一塊較大區域做擔保,當超出Survivor區域容積,將對象拷貝至擔保區域。所以實際內存區是有新生代和老生代之分,新生代快速進行“短命”對象的垃圾回收,老生代區給新生代區做擔保,將比較“長壽”的對象放入老生代。老生代區做老生代的垃圾回收。
3,標記-整理法
標記-復制法適合于新生代對象,而對于老生代對象,標記整理法更好,標記整理法是將“生還”對象移動到內存一端的連續空間。
優點:空間連續,不降低可用內存率。適合老生代。
從上面的討論可以發現,根據內存對象的生命周期特點,應該采取不同的收集方法,因此在實際虛擬機中,都是將內存區分成若干個區域,采用不同的收集方法,已達到整體的最優方案。
垃圾收集器
根據上面的不同收集算法,以及不同性能面向和多線程支持程度,有不同的垃圾收集器實現。垃圾收集器也在向越來越好越來越復雜發展??傮w的目標是:更高吞吐量(運行用戶程序時間/總時間),更高速度,更短獨占線程時間發展。
垃圾回收器圖:
內存分配機制 與垃圾回收器相配合的是內存分配機制:一些內存分配的規則例如:
1,優先在Eden區分配 2,大對象直接放老生代區 3,“長壽”對象放老生代等等
相關資料: http://www.importnew.com/16533.html http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html http://blog.csdn.net/qiutongyeluo/article/details/52901325 《深入理解Java虛擬機》
新聞熱點
疑難解答