垃圾收集器系統有本身的一套方案來判斷哪一個內存塊是應該被回收的,哪一個是不符合要求暫不回收的。垃圾收集器在一個Java程序中的執行是自動的,不能強制執行,即便程序員能明確地判斷出有一塊內存已經無用了,是應該回收的,程序員也不能強制垃圾收集器回收該內存塊。程序員惟一能作的就是經過調用System. gc 方法來"建議"執行垃圾收集器,但其是否能夠執行,何時執行卻都是不可知的。這也是垃圾收集器的最主要的缺點。固然相對於它給程序員帶來的巨大方便性而言,這個缺點是瑕不掩瑜的。程序員
垃圾收集器的主要特色有:
web
1.垃圾收集器的工做目標是回收已經無用的對象的內存空間,從而避免內存滲漏體的產生,節省內存資源,避免程序代碼的崩潰。
算法
2.垃圾收集器判斷一個對象的內存空間是否無用的標準是:若是該對象不能再被程序中任何一個"活動的部分"所引用,此時咱們就說,該對象的內存空間已經無用。所謂"活動的部分",是指程序中某部分參與程序的調用,正在執行過程當中,還沒有執行完畢。
網絡
當一個方法執行完畢,其中的局部變量就會超出使用範圍,此時能夠被看成垃圾收集,但之後每當該方法再次被調用時,其中的局部變量便會被從新建立。函數
3.垃圾收集器線程雖然是做爲低優先級的線程運行,但在系統可用內存量太低的時候,它可能會突發地執行來挽救內存資源。固然其執行與否也是不可預知的。
4.垃圾收集器不能夠被強制執行,但程序員能夠經過調用System. gc方法來建議執行垃圾收集器。
5.不能保證一個無用的對象必定會被垃圾收集器收集,也不能保證垃圾收集器在一段Java語言代碼中必定會執行。所以在程序執行過程當中被分配出去的內存空間可能會一直保留到該程序執行完畢,除非該空間被從新分配或被其餘方法回收。因而可知,徹底完全地根絕內存滲漏體的產生也是不可能的。可是請不要忘記,Java的垃圾收集器畢竟使程序員從手工回收內存空間的繁重工做中解脫了出來。設想一個程序員要用C或C++來編寫一段10萬行語句的代碼,那麼他必定會充分體會到Java的垃圾收集器的優勢!
6.一樣沒有辦法預知在一組均符合垃圾收集器收集標準的對象中,哪個會被首先收集。
7.循環引用對象不會影響其被垃圾收集器收集。 性能
8.能夠經過將對象的引用變量(reference variables,即句柄handles)初始化爲null值,來暗示垃圾收集器來收集該對象。但此時,若是該對象鏈接有事件監聽器(典型的 AWT組件),那它仍是不能夠被收集。因此在設一個引用變量爲null值以前,應注意該引用變量指向的對象是否被監聽,如有,要首先除去監聽器,而後才能夠賦空值。
9.每個對象都有一個finalize( )方法,這個方法是從Object類繼承來的。
10.finalize( )方法用來回收內存之外的系統資源,就像是文件處理器和網絡鏈接器。該方法的調用順序和用來調用該方法的對象的建立順序是無關的。換句話說,書寫程序時該方法的順序和方法的實際調用順序是不相干的。請注意這只是finalize( )方法的特色。
11.每一個對象只能調用finalize( )方法一次。若是在finalize( )方法執行時產生異常(exception),則該對象仍能夠被垃圾收集器收集。
12.垃圾收集器跟蹤每個對象,收集那些不可到達的對象(即該對象沒有被程序的任何"活的部分"所調用),回收其佔有的內存空間。但在進行垃圾收集的時候,垃圾收集器會調用finalize( )方法,經過讓其餘對象知道它的存在,而使不可到達的對象再次"復甦"爲可到達的對象。既然每一個對象只能調用一次finalize( )方法,因此每一個對象也只可能"復甦"一次。
13.finalize( )方法能夠明確地被調用,但它卻不能進行垃圾收集。
14.finalize( )方法能夠被重載(overload),但只有具有初始的finalize( )方法特色的方法才能夠被垃圾收集器調用。
15.子類的finalize( )方法能夠明確地調用父類的finalize( )方法,做爲該子類對象的最後一次適當的操做。但Java編譯器卻不認爲這是一次覆蓋操做(overriding),因此也不會對其調用進行檢查。
16.當finalize( )方法還沒有被調用時,System. runFinalization( )方法能夠用來調用finalize( )方法,並實現相同的效果,對無用對象進行垃圾收集。
17.當一個方法執行完畢,其中的局部變量就會超出使用範圍,此時能夠被看成垃圾收集,但之後每當該方法再次被調用時,其中的局部變量便會被從新建立。
18.Java語言使用了一種"標記交換區的垃圾收集算法"。該算法會遍歷程序中每個對象的句柄,爲被引用的對象作標記,而後回收還沒有作標記的對象。所謂遍歷能夠簡單地理解爲"檢查每個"。
19.Java語言容許程序員爲任何方法添加finalize( )方法,該方法會在垃圾收集器交換回收對象以前被調用。但不要過度依賴該方法對系統資源進行回收和再利用,由於該方法調用後的執行結果是不可預知的。
編碼
總之,在Java語言中,判斷一塊內存空間是否符合垃圾收集器收集標準的標準只有兩個:
1.給對象賦予了空值null,如下再沒有調用過。
2.給對象賦予了新值,既從新分配了內存空間。
最後再次提醒一下,一塊內存空間符合了垃圾收集器的收集標準,並不意味着這塊內存空間就必定會被垃圾收集器收集。
spa
Java語言創建了垃圾收集機制,用以跟蹤正在使用的對象和發現並回收再也不使用(引用)的對象。該機制能夠有效防範動態內存分配中可能發生的兩個危險:因內存垃圾過多而引起的內存耗盡,以及不恰當的內存釋放所形成的內存非法引用。
垃圾收集算法的核心思想是:對虛擬機可用內存空間,即堆空間中的對象進行識別,若是對象正在被引用,那麼稱其爲存活對象,反之,若是對象再也不被引用,則爲垃圾對象,能夠回收其佔據的空間,用於再分配。垃圾收集算法的選擇和垃圾收集系統參數的合理調節直接影響着系統性能,所以須要開發人員作比較深刻的瞭解。
2.觸發主GC(Garbage Collector)的條件
JVM進行次GC的頻率很高,但由於這種GC佔用時間極短,因此對系統產生的影響不大。更值得關注的是主GC的觸發條件,由於它對系統影響很明顯。總的來講,有兩個條件會觸發主GC:
①當應用程序空閒時,即沒有應用線程在運行時,GC會被調用。由於GC在優先級最低的線程中進行,因此當應用忙時,GC線程就不會被調用,但如下條件除外。
②Java堆內存不足時,GC會被調用。當應用線程在運行,並在運行過程當中建立新對象,若這時內存空間不足,JVM就會強制地調用GC線程,以便回收內存用於新的分配。若GC一次以後仍不能知足內存分配的要求,JVM會再進行兩次GC做進一步的嘗試,若仍沒法知足要求,則 JVM將報「out of memory」的錯誤,Java應用將中止。
因爲是否進行主GC由JVM根據系統環境決定,而系統環境在不斷的變化當中,因此主GC的運行具備不肯定性,沒法預計它什麼時候必然出現,但能夠肯定的是對一個長期運行的應用來講,其主GC是反覆進行的。
3.減小GC開銷的措施
根據上述GC的機制,程序的運行會直接影響系統環境的變化,從而影響GC的觸發。若不針對GC的特色進行設計和編碼,就會出現內存駐留等一系列負面影響。爲了不這些影響,基本的原則就是儘量地減小垃圾和減小GC過程當中的開銷。具體措施包括如下幾個方面:
(1)不要顯式調用System.gc()
此函數建議JVM進行主GC,雖然只是建議而非必定,但不少狀況下它會觸發主GC,從而增長主GC的頻率,也即增長了間歇性停頓的次數。
(2)儘可能減小臨時對象的使用
臨時對象在跳出函數調用後,會成爲垃圾,少用臨時變量就至關於減小了垃圾的產生,從而延長了出現上述第二個觸發條件出現的時間,減小了主GC的機會。
(3)對象不用時最好顯式置爲Null
通常而言,爲Null的對象都會被做爲垃圾處理,因此將不用的對象顯式地設爲Null,有利於GC收集器斷定垃圾,從而提升了GC的效率。
(4)儘可能使用StringBuffer,而不用String來累加字符串(詳見blog另外一篇文章Java中String與StringBuffer)
因爲String是固定長的字符串對象,累加String對象時,並不是在一個String對象中擴增,而是從新建立新的String對象,如Str5=Str1+Str2+Str3+Str4,這條語句執行過程當中會產生多個垃圾對象,由於對次做「+」操做時都必須建立新的String對象,但這些過渡對象對系統來講是沒有實際意義的,只會增長更多的垃圾。避免這種狀況能夠改用StringBuffer來累加字符串,因StringBuffer是可變長的,它在原有基礎上進行擴增,不會產生中間對象。
(5)能用基本類型如Int,Long,就不用Integer,Long對象 基本類型變量佔用的內存資源比相應對象佔用的少得多,若是沒有必要,最好使用基本變量。
(6)儘可能少用靜態對象變量
靜態變量屬於全局變量,不會被GC回收,它們會一直佔用內存。
(7)分散對象建立或刪除的時間
集中在短期內大量建立新對象,特別是大對象,會致使忽然須要大量內存,JVM在面臨這種狀況時,只能進行主GC,以回收內存或整合內存碎片,從而增長主GC的頻率。集中刪除對象,道理也是同樣的。它使得忽然出現了大量的垃圾對象,空閒空間必然減小,從而大大增長了下一次建立新對象時強制主GC的機會。
5.Java 內存泄漏
因爲採用了垃圾回收機制,任何不可達對象(對象再也不被引用)均可以由垃圾收集線程回收。所以一般說的Java 內存泄漏實際上是指無心識的、非故意的對象引用,或者無心識的對象保持。無心識的對象引用是指代碼的開發人員原本已經對對象使用完畢,卻由於編碼的錯誤而意外地保存了對該對象的引用(這個引用的存在並非編碼人員的主觀意願),從而使得該對象一直沒法被垃圾回收器回收掉,這種原本覺得能夠釋放掉的卻最終未能被釋放的空間能夠認爲是被「泄漏了」。
考慮下面的程序,在ObjStack類中,使用push和pop方法來管理堆棧中的對象。兩個方法中的索引(index)用於指示堆棧中下一個可用位置。push方法存儲對新對象的引用並增長索引值,而pop方法減少索引值並返回堆棧最上面的元素。在main方法中,建立了容量爲64的棧,並64次調用push方法向它添加對象,此時index的值爲64,隨後又32次調用pop方法,則index的值變爲32,出棧意味着在堆棧中的空間應該被收集。但事實上,pop方法只是減少了索引值,堆棧仍然保持着對那些對象的引用。故32個無用對象不會被GC回收,形成了內存滲漏。
線程
經過以上對垃圾收集器特色的瞭解,你應該能夠明確垃圾收集器的做用,和垃圾收集器判斷一塊內存空間是否無用的標準。簡單地說,當你爲一個對象賦值爲null而且從新定向了該對象的引用者,此時該對象就符合垃圾收集器的收集標準。
判斷一個對象是否符合垃圾收集器的收集標準,這是SUN公司程序員認證考試中垃圾收集器部分的重要考點(能夠說,這是惟一的考點)。因此,考生在一段給定的代碼中,應該可以判斷出哪一個對象符合垃圾收集器收集的標準,哪一個不符合。下面結合幾種認證考試中可能出現的題型來具體講解:
Object obj = new Object ( )
咱們知道,obj爲Object的一個句柄。當出現new關鍵字時,就給新建的對象分配內存空間,而obj的值就是新分配的內存空間的首地址,即該對象的值(請特別注意,對象的值和對象的內容是不一樣含義的兩個概念:對象的值就是指其內存塊的首地址,即對象的句柄;而對象的內容則是其具體的內存塊)。此時若是有 obj = null; 則obj指向的內存塊此時就無用了,由於下面再沒有調用該變量了。
請再看如下三種認證考試時可能出現的題型:
程序段1:
1.fobj = new Object ( )
2.fobj. Method ( )
3.fobj = new Object ( )
4.fobj. Method ( )
問:這段代碼中,第幾行的fobj 符合垃圾收集器的收集標準?
答:第3行。由於第3行的fobj被賦了新值,產生了一個新的對象,即換了一塊新的內存空間,也至關於爲第1行中的fobj賦了null值。這種類型的題在認證0考試中是最簡單的。
程序段2:
1.Object sobj = new Object ( )
2.Object sobj = null
3.Object sobj = new Object ( )
4.sobj = new Object ( )
問:這段代碼中,第幾行的內存空間符合垃圾收集器的收集標準?
答:第1行和第3行。由於第2行爲sobj賦值爲null,因此在此第1行的sobj符合垃圾收集器的收集標準。而第4行至關於爲sobj賦值爲null,因此在此第3行的sobj也符合垃圾收集器的收集標準。
若是有一個對象的句柄a,且你把a做爲某個構造器的參數,
即 new Constructor ( a )的時候,即便你給a賦值爲null,a也不符合垃圾收集器的收集標準。直到由上面構造器構造的新對象被賦空值時,a才能夠被垃圾收集器收集。
程序段3:
1.Object aobj = new Object ( )
2.Object bobj = new Object ( )
3.Object cobj = new Object ( )
4.aobj = bobj;
5.aobj = cobj;
6.cobj = null;
7.aobj = null;
問:這段代碼中,第幾行的內存空間符合垃圾收集器的收集標準?
答:第4行,第7行。注意這類題型是認證考試中可能遇到的最難題型了。
行1-3分別建立了Object類的三個對象:aobj,bobj,cobj
行4:此時對象aobj的句柄指向bobj,aobj原來指向的對象沒有了引用,因此符合。 (原帖這裏說的有錯)
行7:對象cobj符合了垃圾收集器的收集標準,由於cobj的句柄指向單一的地址空間。在第6行的時候,cobj已經被賦值爲null,但由cobj同時還指向了aobj(第5行),因此此時cobj並不符合垃圾收集器的收集標準。而在第7行,aobj所指向的地址空間也被賦予了空值null,這就說明了,由cobj所指向的地址空間已經被徹底地賦予了空值。因此此時cobj最終符合了垃圾收集器的收集標準。 設計