Java垃圾回收機制

之前沒有寫博客的習慣,學過的東西又不能一直都記着,因此用到「方現查」,每次都要看不少東西,才能把某一方面的東西看全。html

對於Java垃圾回收機制,這個好久前就學過,而且理解過了,好長時間不用,就丟到回爪窪島了,這裏仍是記下,方便之後再次查看,不用浪費太多時間。java

瞭解Java垃圾回收機制,就要知道Java各個版本的區別,尤爲是隨着JDK版本的提高,都比較之前版本有哪些改進。最近,尤爲是JDK1.7中加入了G1,這個是增長的新的回收方式,起始在JDK1.6 40左右的版本的時候就已經加入實驗性的G1了。程序員

線面是我轉發的博文,本身寫沒那麼多時間,有兩篇不錯,兩篇各有互補點。算法

第一篇:說的比較多,但不是太詳細

http://www.cnblogs.com/laoyangHJ/articles/java_gc.html數組

垃圾收集GC(Garbage Collection)是Java語言的核心技術之一,以前咱們曾專門探討過Java 7新增的垃圾回收器G1的新特性,但在JVM的內部運行機制上看,Java的垃圾回收原理與機制並未改變。垃圾收集的目的在於清除再也不使用的對象。GC經過肯定對象是否被活動對象引用來肯定是否收集該對象。GC首先要判斷該對象是不是時候能夠收集。兩種經常使用的方法是引用計數和對象引用遍歷。多線程

總的來講,可分爲兩大類

引用計數收集器併發

引用計數是垃圾收集器中的早期策略。在這種方法中,堆中每一個對象(不是引用)都有一個引用計數。當一個對象被建立時,且將該對象分配給一個變量,該變量計數設置爲1。當任何其它變量被賦值爲這個對象的引用時,計數加1(a = b,則b引用的對象+1),但當一個對象的某個引用超過了生命週期或者被設置爲一個新值時,對象的引用計數減1。任何引用計數爲0的對象能夠被看成垃圾收集。當一個對象被垃圾收集時,它引用的任何對象計數減1。post

優勢:引用計數收集器能夠很快的執行,交織在程序運行中。對程序不被長時間打斷的實時環境比較有利。性能

缺點: 沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能爲0.優化

跟蹤收集器

早期的JVM使用引用計數,如今大多數JVM採用對象引用遍歷。對象引用遍歷從一組對象開始,沿着整個對象圖上的每條連接,遞歸肯定可到達(reachable)的對象。若是某對象不能從這些根對象的一個(至少一個)到達,則將它做爲垃圾收集。在對象遍歷階段,GC必須記住哪些對象能夠到達,以便刪除不可到達的對象,這稱爲標記(marking)對象。

下一步,GC要刪除不可到達的對象。刪除時,有些GC只是簡單的掃描堆棧,刪除未標記的未標記的對象,並釋放它們的內存以生成新的對象,這叫作清除(sweeping)。這種方法的問題在於內存會分紅好多小段,而它們不足以用於新的對象,可是組合起來卻很大。所以,許多GC能夠從新組織內存中的對象,並進行壓縮(compact),造成可利用的空間。

爲此,GC須要中止其餘的活動活動。這種方法意味着全部與應用程序相關的工做中止,只有GC運行。結果,在響應期間增減了許多混雜請求。另外,更復雜的 GC不斷增長或同時運行以減小或者清除應用程序的中斷。有的GC使用單線程完成這項工做,有的則採用多線程以增長效率。

實際上GC判斷對象是否可達看的是強引用。更準確的描述是,一個對象存在強引用,一定是從其它強引用對象的本地變量,靜態變量或者其它相似的地方直接引用過來的。換句話說,若是一堆對象經過某個不存活的對象「強引用」過來的話,它們會被一塊兒回收掉。

一些具體經常使用的垃圾收集算法

(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內存結構,它是由堆、棧、本地方法棧、方法區等部分組成,結構圖以下所示。

JVM內存組成結構

1)堆

全部經過new建立的對象的內存都在堆中分配,其大小能夠經過-Xmx和-Xms來控制。堆被劃分爲新生代和舊生代,新生代又被進一步劃分爲Eden和Survivor區,最後Survivor由FromSpace和ToSpace組成,結構圖以下所示:

JVM內存結構之堆

新生代。新建的對象都是用新生代分配內存,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的異常,以下圖所示:

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機制是須要組合使用的,指定方式由下表所示:

GC機制組合使用

第二篇:詳細介紹了垃圾回收機制的算法

轉自:http://www.cnblogs.com/dolphin0520/p/3783345.html

說到垃圾回收(Garbage Collection,GC),不少人就會天然而然地把它和Java聯繫起來。在Java中,程序員不須要去關心內存動態分配和垃圾回收的問題,這一切都交給了JVM來處理。顧名思義,垃圾回收就是釋放垃圾佔用的空間,那麼在Java中,什麼樣的對象會被認定爲「垃圾」?那麼當一些對象被肯定爲垃圾以後,採用什麼樣的策略來進行回收(釋放空間)?在目前的商業虛擬機中,有哪些典型的垃圾收集器?下面咱們就來逐一探討這些問題。如下是本文的目錄大綱:

  一.如何肯定某個對象是「垃圾」?

  二.典型的垃圾收集算法

  三.典型的垃圾收集器

一.如何肯定某個對象是「垃圾」?

  在這一小節咱們先了解一個最基本的問題:若是肯定某個對象是「垃圾」?既然垃圾收集器的任務是回收垃圾對象所佔的空間供新的對象使用,那麼垃圾收集器如何肯定某個對象是「垃圾」?—即經過什麼方法判斷一個對象能夠被回收了。

  在java中是經過引用來和對象進行關聯的,也就是說若是要操做對象,必須經過引用來進行。那麼很顯然一個簡單的辦法就是經過引用計數來判斷一個對象是否能夠被回收。不失通常性,若是一個對象沒有任何引用與之關聯,則說明該對象基本不太可能在其餘地方被使用到,那麼這個對象就成爲可被回收的對象了。這種方式成爲引用計數法。

  這種方式的特色是實現簡單,並且效率較高,可是它沒法解決循環引用的問題,所以在Java中並無採用這種方式(Python採用的是引用計數法)。看下面這段代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public  class  Main {
     public  static  void  main(String[] args) {
         MyObject object1 = new  MyObject();
         MyObject object2 = new  MyObject();
         
         object1.object = object2;
         object2.object = object1;
         
         object1 = null ;
         object2 = null ;
     }
}
 
class  MyObject{
     public  Object object = null ;
}

  最後面兩句將object1和object2賦值爲null,也就是說object1和object2指向的對象已經不可能再被訪問,可是因爲它們互相引用對方,致使它們的引用計數都不爲0,那麼垃圾收集器就永遠不會回收它們。

  爲了解決這個問題,在Java中採起了 可達性分析法。該方法的基本思想是經過一系列的「GC Roots」對象做爲起點進行搜索,若是在「GC Roots」和一個對象之間沒有可達路徑,則稱該對象是不可達的,不過要注意的是被斷定爲不可達的對象不必定就會成爲可回收對象。被斷定爲不可達的對象要成爲可回收對象必須至少經歷兩次標記過程,若是在這兩次標記過程當中仍然沒有逃脫成爲可回收對象的可能性,則基本上就真的成爲可回收對象了。

  至於可達性分析法具體是如何操做的我暫時也沒有看得很明白,若是有哪位朋友比較清楚的話請不吝指教。

  下面來看個例子:

1
2
3
4
5
6
7
Object aobj = new  Object ( ) ;
Object bobj = new  Object ( ) ;
Object cobj = new  Object ( ) ;
aobj = bobj;
aobj = cobj;
cobj = null ;
aobj = null ;

   第幾行有可能會使得某個對象成爲可回收對象?第7行的代碼會致使有對象會成爲可回收對象。至於爲何留給讀者本身思考。

  再看一個例子:

1
2
3
String str = new  String( "hello" );
SoftReference<String> sr = new  SoftReference<String>( new  String( "java" ));
WeakReference<String> wr = new  WeakReference<String>( new  String( "world" ));

  這三句哪句會使得String對象成爲可回收對象?第2句和第3句,第2句在內存不足的狀況下會將String對象斷定爲可回收對象,第3句不管什麼狀況下String對象都會被斷定爲可回收對象。

  最後總結一下日常遇到的比較常見的將對象斷定爲可回收對象的狀況:

  1)顯示地將某個引用賦值爲null或者將已經指向某個對象的引用指向新的對象,好比下面的代碼:

1
2
3
4
5
Object obj = new  Object();
obj = null ;
Object obj1 = new  Object();
Object obj2 = new  Object();
obj1 = obj2;

    2)局部引用所指向的對象,好比下面這段代碼:

1
2
3
4
5
6
7
8
void  fun() {
 
.....
     for ( int  i= 0 ;i< 10 ;i++) {
         Object obj = new  Object();
         System.out.println(obj.getClass());
     }   
}

   循環每執行完一次,生成的Object對象都會成爲可回收的對象。

  3)只有弱引用與其關聯的對象,好比:

1
WeakReference<String> wr = new  WeakReference<String>( new  String( "world" ));

二.典型的垃圾收集算法

  在肯定了哪些垃圾能夠被回收後,垃圾收集器要作的事情就是開始進行垃圾回收,可是這裏面涉及到一個問題是:如何高效地進行垃圾回收。因爲Java虛擬機規範並無對如何實現垃圾收集器作出明確的規定,所以各個廠商的虛擬機能夠採用不一樣的方式來實現垃圾收集器,因此在此只討論幾種常見的垃圾收集算法的核心思想。

  1.Mark-Sweep(標記-清除)算法

  這是最基礎的垃圾回收算法,之因此說它是最基礎的是由於它最容易實現,思想也是最簡單的。標記-清除算法分爲兩個階段:標記階段和清除階段。標記階段的任務是標記出全部須要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。具體過程以下圖所示:

  從圖中能夠很容易看出標記-清除算法實現起來比較容易,可是有一個比較嚴重的問題就是容易產生內存碎片,碎片太多可能會致使後續過程當中須要爲大對象分配空間時沒法找到足夠的空間而提早觸發新的一次垃圾收集動做。

  2.Copying(複製)算法

  爲了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來。它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。具體過程以下圖所示:

  這種算法雖然實現簡單,運行高效且不容易產生內存碎片,可是卻對內存空間的使用作出了高昂的代價,由於可以使用的內存縮減到原來的一半。

  很顯然,Copying算法的效率跟存活對象的數目多少有很大的關係,若是存活對象不少,那麼Copying算法的效率將會大大下降。

  3.Mark-Compact(標記-整理)算法

  爲了解決Copying算法的缺陷,充分利用內存空間,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep同樣,可是在完成標記以後,它不是直接清理可回收對象,而是將存活對象都向一端移動,而後清理掉端邊界之外的內存。具體過程以下圖所示:

  

  4.Generational Collection(分代收集)算法

  分代收集算法是目前大部分JVM的垃圾收集器採用的算法。它的核心思想是根據對象存活的生命週期將內存劃分爲若干個不一樣的區域。通常狀況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),老年代的特色是每次垃圾收集時只有少許對象須要被回收,而新生代的特色是每次垃圾回收時都有大量的對象須要被回收,那麼就能夠根據不一樣代的特色採起最適合的收集算法。

  目前大部分垃圾收集器對於新生代都採起Copying算法,由於新生代中每次垃圾回收都要回收大部分對象,也就是說須要複製的操做次數較少,可是實際中並非按照1:1的比例來劃分新生代的空間的,通常來講是將新生代劃分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另外一塊Survivor空間中,而後清理掉Eden和剛纔使用過的Survivor空間。

  而因爲老年代的特色是每次回收都只回收少許對象,通常使用的是Mark-Compact算法。

  注意,在堆區以外還有一個代就是永久代(Permanet Generation),它用來存儲class類、常量、方法描述等。對永久代的回收主要回收兩部份內容:廢棄常量和無用的類。

三.典型的垃圾收集器

  垃圾收集算法是 內存回收的理論基礎,而垃圾收集器就是內存回收的具體實現。下面介紹一下HotSpot(JDK 7)虛擬機提供的幾種垃圾收集器,用戶能夠根據本身的需求組合出各個年代使用的收集器。

  1.Serial/Serial Old

  Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,而且在它進行垃圾收集時,必須暫停全部用戶線程。Serial收集器是針對新生代的收集器,採用的是Copying算法,Serial Old收集器是針對老年代的收集器,採用的是Mark-Compact算法。它的優勢是實現簡單高效,可是缺點是會給用戶帶來停頓。

  2.ParNew

  ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。

  3.Parallel Scavenge

  Parallel Scavenge收集器是一個新生代的多線程收集器(並行收集器),它在回收期間不須要暫停其餘用戶線程,其採用的是Copying算法,該收集器與前兩個收集器有所不一樣,它主要是爲了達到一個可控的吞吐量。

  4.Parallel Old

  Parallel Old是Parallel Scavenge收集器的老年代版本(並行收集器),使用多線程和Mark-Compact算法。

  5.CMS

  CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器,它是一種併發收集器,採用的是Mark-Sweep算法。

  6.G1

  G1收集器是當今收集器技術發展最前沿的成果,它是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。所以它是一款並行與併發收集器,而且它能創建可預測的停頓時間模型。

  下面補充一下關於內存分配方面的東西:

  

  對象的內存分配,往大方向上講就是在堆上分配,對象主要分配在新生代的Eden Space和From Space,少數狀況下會直接分配在老年代。若是新生代的Eden Space和From Space的空間不足,則會發起一次GC,若是進行了GC以後,Eden Space和From Space可以容納該對象就放在Eden Space和From Space。在GC的過程當中,會將Eden Space和From  Space中的存活對象移動到To Space,而後將Eden Space和From Space進行清理。若是在清理的過程當中,To Space沒法足夠來存儲某個對象,就會將該對象移動到老年代中。在進行了GC以後,使用的即是Eden space和To Space了,下次GC時會將存活對象複製到From Space,如此反覆循環。當對象在Survivor區躲過一次GC的話,其對象年齡便會加1,默認狀況下,若是對象年齡達到15歲,就會移動到老年代中。

  通常來講,大對象會被直接分配到老年代,所謂的大對象是指須要大量連續存儲空間的對象,最多見的一種大對象就是大數組,好比:

  byte[] data = new byte[4*1024*1024]

  這種通常會直接在老年代分配存儲空間。

  固然分配的規則並非百分之百固定的,這要取決於當前使用的是哪一種垃圾收集器組合和JVM的相關參數。