垃圾收集GC(Garbage Collection)是Java語言的核心技術之一,以前咱們曾專門探討過Java 7新增的垃圾回收器G1的新特性,但在JVM的內部運行機制上看,Java的垃圾回收原理與機制並未改變。垃圾收集的目的在於清除再也不使用的對象。GC經過肯定對象是否被活動對象引用來肯定是否收集該對象。GC首先要判斷該對象是不是時候能夠收集。兩種經常使用的方法是引用計數和對象引用遍歷。java
引用計數收集器程序員
引用計數是垃圾收集器中的早期策略。在這種方法中,堆中每一個對象(不是引用)都有一個引用計數。當一個對象被建立時,且將該對象分配給一個變量,該變量計數設置爲1。當任何其它變量被賦值爲這個對象的引用時,計數加1(a = b,則b引用的對象+1),但當一個對象的某個引用超過了生命週期或者被設置爲一個新值時,對象的引用計數減1。任何引用計數爲0的對象能夠被看成垃圾收集。當一個對象被垃圾收集時,它引用的任何對象計數減1。算法
優勢:引用計數收集器能夠很快的執行,交織在程序運行中。對程序不被長時間打斷的實時環境比較有利。多線程
缺點: 沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能爲0.併發
跟蹤收集器性能
早期的JVM使用引用計數,如今大多數JVM採用對象引用遍歷。對象引用遍歷從一組對象開始,沿着整個對象圖上的每條連接,遞歸肯定可到達(reachable)的對象。若是某對象不能從這些根對象的一個(至少一個)到達,則將它做爲垃圾收集。在對象遍歷階段,GC必須記住哪些對象能夠到達,以便刪除不可到達的對象,這稱爲標記(marking)對象。優化
下一步,GC要刪除不可到達的對象。刪除時,有些GC只是簡單的掃描堆棧,刪除未標記的未標記的對象,並釋放它們的內存以生成新的對象,這叫作清除(sweeping)。這種方法的問題在於內存會分紅好多小段,而它們不足以用於新的對象,可是組合起來卻很大。所以,許多GC能夠從新組織內存中的對象,並進行壓縮(compact),造成可利用的空間。線程
爲此,GC須要中止其餘的活動活動。這種方法意味着全部與應用程序相關的工做中止,只有GC運行。結果,在響應期間增減了許多混雜請求。另外,更復雜的 GC不斷增長或同時運行以減小或者清除應用程序的中斷。有的GC使用單線程完成這項工做,有的則採用多線程以增長效率。3d
一些經常使用的垃圾收集器指針
(1)標記-清除收集器
這種收集器首先遍歷對象圖並標記可到達的對象,而後掃描堆棧以尋找未標記對象並釋放它們的內存。這種收集器通常使用單線程工做並中止其餘操做。而且,因爲它只是清除了那些未標記的對象,而並無對標記對象進行壓縮,致使會產生大量內存碎片,從而浪費內存。
(2)標記-壓縮收集器
有時也叫標記-清除-壓縮收集器,與標記-清除收集器有相同的標記階段。在第二階段,則把標記對象複製到堆棧的新域中以便壓縮堆棧。這種收集器也中止其餘操做。
(3)複製收集器
這種收集器將堆棧分爲兩個域,常稱爲半空間。每次僅使用一半的空間,JVM生成的新對象則放在另外一半空間中。GC運行時,它把可到達對象複製到另外一半空間,從而壓縮了堆棧。這種方法適用於短生存期的對象,持續複製長生存期的對象則致使效率下降。而且對於指定大小堆來講,須要兩倍大小的內存,由於任什麼時候候都只使用其中的一半。
(4) 增量收集器
增量收集器把堆棧分爲多個域,每次僅從一個域收集垃圾,也可理解爲把堆棧分紅一小塊一小塊,每次僅對某一個塊進行垃圾收集。這會形成較小的應用程序中斷時間,使得用戶通常不能覺察到垃圾收集器正在工做。
(5)分代收集器
複製收集器的缺點是:每次收集時,全部的標記對象都要被拷貝,從而致使一些生命週期很長的對象被來回拷貝屢次,消耗大量的時間。而分代收集器則可解決這個問題,分代收集器把堆棧分爲兩個或多個域,用以存放不一樣壽命的對象。JVM生成的新對象通常放在其中的某個域中。過一段時間,繼續存在的對象(非短命對象)將得到使用期並轉入更長壽命的域中。分代收集器對不一樣的域使用不一樣的算法以優化性能。
並行收集器
並行收集器使用某種傳統的算法並使用多線程並行的執行它們的工做。在多CPU機器上使用多線程技術能夠顯著的提升java應用程序的可擴展性。
最後,貼出一個很是簡單的跟蹤收集器的例圖,以便你們加深對收集器的理解:
跟蹤收集器圖例
使用垃圾收集器要注意的地方
下面將提出一些有關垃圾收集器要注意的地方,垃圾收集器知識不少,下面只列出一部分必要的知識:
(1)每一個對象只能調用finalize( )方法一次。若是在finalize( )方法執行時產生異常(exception),則該對象仍能夠被垃圾收集器收集。
(2)垃圾收集器跟蹤每個對象,收集那些不可觸及的對象(即該對象再也不被程序引用 了),回收其佔有的內存空間。但在進行垃圾收集的時候,垃圾收集器會調用該對象的finalize( )方法(若是有)。若是在finalize()方法中,又使得該對象被程序引用(俗稱復活了),則該對象就變成了可觸及的對象,暫時不會被垃圾收集了。可是因爲每一個對象只能調用一次finalize( )方法,因此每一個對象也只可能 "復活 "一次。
(3)Java語言容許程序員爲任何方法添加finalize( )方法,該方法會在垃圾收集器交換回收對象以前被調用。但不要過度依賴該方法對系統資源進行回收和再利用,由於該方法調用後的執行結果是不可預知的。
(4)垃圾收集器不能夠被強制執行,但程序員能夠經過調研System.gc方法來建議執行垃圾收集。記住,只是建議。通常不建議本身寫System.gc,由於會加大垃圾收集工做量。
詳解Java GC的工做原理
概要: JVM內存結構由堆、棧、本地方法棧、方法區等部分組成,另外JVM分別對新生代和舊生代採用不一樣的垃圾回收機制。
1. 首先來看一下JVM內存結構,它是由堆、棧、本地方法棧、方法區等部分組成,結構圖以下所示。
1)堆
全部經過new建立的對象的內存都在堆中分配,其大小能夠經過-Xmx和-Xms來控制。堆被劃分爲新生代和舊生代,新生代又被進一步劃分爲Eden和Survivor區,最後Survivor由FromSpace和ToSpace組成,結構圖以下所示:
新生代。新建的對象都是用新生代分配內存,Eden空間不足的時候,會把存活的對象轉移到Survivor中,新生代大小能夠由-Xmn來控制,也能夠用-XX:SurvivorRatio來控制Eden和Survivor的比例舊生代。用於存放新生代中通過屢次垃圾回收仍然存活的對象
2)棧
每一個線程執行每一個方法的時候都會在棧中申請一個棧幀,每一個棧幀包括局部變量區和操做數棧,用於存放這次方法調用過程當中的臨時變量、參數和中間結果
3)本地方法棧
用於支持native方法的執行,存儲了每一個native方法調用的狀態
4)方法區
存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(PermanetGeneration)來存放方法區,可經過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。介紹完了JVM內存組成結構,下面咱們再來看一下JVM垃圾回收機制。
2. JVM垃圾回收機制
JVM分別對新生代和舊生代採用不一樣的垃圾回收機制
新生代的GC:
新生代一般存活時間較短,所以基於Copying算法來進行回收,所謂Copying算法就是掃描出存活的對象,並複製到一塊新的徹底未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace之間copy。新生代採用空閒指針的方式來控制GC觸發,指針保持最後一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從eden到survivor,最後到舊生代,
用javavisualVM來查看,能明顯觀察到新生代滿了後,會把對象轉移到舊生代,而後清空繼續裝載,當舊生代也滿了後,就會報outofmemory的異常,以下圖所示:
在執行機制上JVM提供了串行GC(SerialGC)、並行回收GC(ParallelScavenge)和並行GC(ParNew)
1)串行GC
在整個掃描和複製過程採用單線程的方式來進行,適用於單CPU、新生代空間較小及對暫停時間要求不是很是高的應用上,是client級別默認的GC方式,能夠經過-XX:+UseSerialGC來強制指定
2)並行回收GC
在整個掃描和複製過程採用多線程的方式來進行,適用於多CPU、對暫停時間要求較短的應用上,是server級別默認採用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定線程數
3)並行GC
與舊生代的併發GC配合使用
舊生代的GC:
舊生代與新生代不一樣,對象存活的時間比較長,比較穩定,所以採用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,而後再進行回收未被標記的對象,回收後對用空出的空間要麼進行合併,要麼標記出來便於下次進行分配,總之就是要減小內存碎片帶來的效率損耗。在執行機制上JVM提供了串行GC(SerialMSC)、並行GC(parallelMSC)和併發GC(CMS),具體算法細節還有待進一步深刻研究。
以上各類GC機制是須要組合使用的,指定方式由下表所示: