1.垃圾收集算法的核心思想java
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是反覆進行的。ide
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的機會。
4.gc與finalize方法
⑴gc方法請求垃圾回收
使用System.gc()能夠無論JVM使用的是哪種垃圾回收的算法,均可以請求Java的垃圾回收。須要注意的是,調用System.gc()也僅僅是一個請求。JVM接受這個消息後,並非當即作垃圾回收,而只是對幾個垃圾回收算法作了加權,使垃圾回收操做容易發生,或提前發生,或回收較多而已。
⑵finalize方法透視垃圾收集器的運行
在JVM垃圾收集器收集一個對象以前 ,通常要求程序調用適當的方法釋放資源,但在沒有明確釋放資源的狀況下,Java提供了缺省機制來終止化該對象釋放資源,這個方法就是finalize()。它的原型爲:
protected void finalize() throws Throwable
在finalize()方法返回以後,對象消失,垃圾收集開始執行。原型中的throws Throwable表示它能夠拋出任何類型的異常。
所以,當對象即將被銷燬時,有時須要作一些善後工做。能夠把這些操做寫在finalize()方法裏。
⑶代碼示例
5.Java 內存泄漏
因爲採用了垃圾回收機制,任何不可達對象(對象再也不被引用)均可以由垃圾收集線程回收。所以一般說的Java 內存泄漏實際上是指無心識的、非故意的對象引用,或者無心識的對象保持。無心識的對象引用是指代碼的開發人員原本已經對對象使用完畢,卻由於編碼的錯誤而意外地保存了對該對象的引用(這個引用的存在並非編碼人員的主觀意願),從而使得該對象一直沒法被垃圾回收器回收掉,這種原本覺得能夠釋放掉的卻最終未能被釋放的空間能夠認爲是被「泄漏了」。
考慮下面的程序,在ObjStack類中,使用push和pop方法來管理堆棧中的對象。兩個方法中的索引(index)用於指示堆棧中下一個可用位置。push方法存儲對新對象的引用並增長索引值,而pop方法減少索引值並返回堆棧最上面的元素。在main方法中,建立了容量爲64的棧,並64次調用push方法向它添加對象,此時index的值爲64,隨後又32次調用pop方法,則index的值變爲32,出棧意味着在堆棧中的空間應該被收集。但事實上,pop方法只是減少了索引值,堆棧仍然保持着對那些對象的引用。故32個無用對象不會被GC回收,形成了內存滲漏。
6.如何消除內存泄漏
雖然Java虛擬機(JVM)及其垃圾收集器(garbage collector,GC)負責管理大多數的內存任務,Java軟件程序中仍是有可能出現內存泄漏。實際上,這在大型項目中是一個常見的問題。避免內存泄漏的第一步是要弄清楚它是如何發生的。本文介紹了編寫Java代碼的一些常見的內存泄漏陷阱,以及編寫不泄漏代碼的一些最佳實踐。一旦發生了內存泄漏,要指出形成泄漏的代碼是很是困難的。所以本文還介紹了一種新工具,用來診斷泄漏並指出根本緣由。該工具的開銷很是小,所以可使用它來尋找處於生產中的系統的內存泄漏。
垃圾收集器的做用
雖然垃圾收集器處理了大多數內存管理問題,從而使編程人員的生活變得更輕鬆了,可是編程人員仍是可能犯錯而致使出現內存問題。簡單地說,GC循環地跟蹤全部來自「根」對象(堆棧對象、靜態對象、JNI句柄指向的對象,諸如此類)的引用,並將全部它所能到達的對象標記爲活動的。程序只能夠操縱這些對象;其餘的對象都被刪除了。由於GC使程序不可能到達已被刪除的對象,這麼作就是安全的。
雖然內存管理能夠說是自動化的,可是這並不能使編程人員免受思考內存管理問題之苦。例如,分配(以及釋放)內存總會有開銷,雖然這種開銷對編程人員來講是不可見的。建立了太多對象的程序將會比完成一樣的功能而建立的對象卻比較少的程序更慢一些(在其餘條件相同的狀況下)。
並且,與本文更爲密切相關的是,若是忘記「釋放」先前分配的內存,就可能形成內存泄漏。若是程序保留對永遠再也不使用的對象的引用,這些對象將會佔用並耗盡內存,這是由於自動化的垃圾收集器沒法證實這些對象將再也不使用。正如咱們先前所說的,若是存在一個對對象的引用,對象就被定義爲活動的,所以不能刪除。爲了確保能回收對象佔用的內存,編程人員必須確保該對象不能到達。這一般是經過將對象字段設置爲null或者從集合(collection)中移除對象而完成的。可是,注意,當局部變量再也不使用時,沒有必要將其顯式地設置爲null。對這些變量的引用將隨着方法的退出而自動清除。
歸納地說,這就是內存託管語言中的內存泄漏產生的主要緣由:保留下來卻永遠再也不使用的對象引用。
典型泄漏
既然咱們知道了在Java中確實有可能發生內存泄漏,就讓咱們來看一些典型的內存泄漏及其緣由。
全局集合
在大的應用程序中有某種全局的數據儲存庫是很常見的,例如一個JNDI樹或一個會話表。在這些狀況下,必須注意管理儲存庫的大小。必須有某種機制從儲存庫中移除再也不須要的數據。
這可能有多種方法,可是最多見的一種是週期性運行的某種清除任務。該任務將驗證儲存庫中的數據,並移除任何再也不須要的數據。
另外一種管理儲存庫的方法是使用反向連接(referrer)計數。而後集合負責統計集合中每一個入口的反向連接的數目。這要求反向連接告訴集合什麼時候會退出入口。當反向連接數目爲零時,該元素就能夠從集合中移除了。
緩存
緩存是一種數據結構,用於快速查找已經執行的操做的結果。所以,若是一個操做執行起來很慢,對於經常使用的輸入數據,就能夠將操做的結果緩存,並在下次調用該操做時使用緩存的數據。
緩存一般都是以動態方式實現的,其中新的結果是在執行時添加到緩存中的。典型的算法是:
檢查結果是否在緩存中,若是在,就返回結果。
若是結果不在緩存中,就進行計算。
將計算出來的結果添加到緩存中,以便之後對該操做的調用可使用。
該算法的問題(或者說是潛在的內存泄漏)出在最後一步。若是調用該操做時有至關多的不一樣輸入,就將有至關多的結果存儲在緩存中。很明顯這不是正確的方法。
爲了預防這種具備潛在破壞性的設計,程序必須確保對於緩存所使用的內存容量有一個上限。所以,更好的算法是:
檢查結果是否在緩存中,若是在,就返回結果。
若是結果不在緩存中,就進行計算。
若是緩存所佔的空間過大,就移除緩存最久的結果。
將計算出來的結果添加到緩存中,以便之後對該操做的調用可使用。
經過始終移除緩存最久的結果,咱們實際上進行了這樣的假設:在未來,比起緩存最久的數據,最近輸入的數據更有可能用到。這一般是一個不錯的假設。
新算法將確保緩存的容量處於預約義的內存範圍以內。確切的範圍可能很難計算,由於緩存中的對象在不斷變化,並且它們的引用一應俱全。爲緩存設置正確的大小是一項很是複雜的任務,須要將所使用的內存容量與檢索數據的速度加以平衡。
解決這個問題的另外一種方法是使用java.lang.ref.SoftReference類跟蹤緩存中的對象。這種方法保證這些引用可以被移除,若是虛擬機的內存用盡而須要更多堆的話。
ClassLoader
Java ClassLoader結構的使用爲內存泄漏提供了許多可乘之機。正是該結構自己的複雜性使ClassLoader在內存泄漏方面存在如此多的問題。ClassLoader的特別之處在於它不只涉及「常規」的對象引用,還涉及元對象引用,好比:字段、方法和類。這意味着只要有對字段、方法、類或ClassLoader的對象的引用,ClassLoader就會駐留在JVM中。由於ClassLoader自己能夠關聯許多類及其靜態字段,因此就有許多內存被泄漏了。
肯定泄漏的位置
一般發生內存泄漏的第一個跡象是:在應用程序中出現了OutOfMemoryError。這一般發生在您最不肯意它發生的生產環境中,此時幾乎不能進行調試。有多是由於測試環境運行應用程序的方式與生產系統不徹底相同,於是致使泄漏只出如今生產中。在這種狀況下,須要使用一些開銷較低的工具來監控和查找內存泄漏。還須要可以無需重啓系統或修改代碼就能夠將這些工具鏈接到正在運行的系統上。可能最重要的是,當進行分析時,須要可以斷開工具而保持系統不受干擾。
雖然OutOfMemoryError一般都是內存泄漏的信號,可是也有可能應用程序確實正在使用這麼多的內存;對於後者,或者必須增長JVM可用的堆的數量,或者對應用程序進行某種更改,使它使用較少的內存。可是,在許多狀況下,OutOfMemoryError都是內存泄漏的信號。一種查明方法是不間斷地監控GC的活動,肯定內存使用量是否隨着時間增長。若是確實如此,就可能發生了內存泄漏。