JVM的組成、垃圾回收機制

轉:http://www.cnblogs.com/wabi87547568/p/5282892.html

1.JVM的組成 

JVM定義了控制Java代碼解釋執行和具體實現的五種規格,因此把JVM分成了6個部分:JVM解釋器、指令系統、寄存器、棧、存儲區和碎片回收區。

◆JVM解釋器:即這個虛擬機處理字段碼的CPU。 

◆JVM指令系統:該系統與計算機很相似,一條指令由操作碼和操作數兩部分組成。操作碼爲8位二進制數,主要是爲了說明一條指令的功能,操作數可以根據需要而定,JVM最多有256種不同的操作指令。目前已使用了160多種操作碼。 

◆寄存器:JVM有自己的虛擬寄存器,這樣就可以快速地與JVM的解釋器進行數據交換。爲了功能的需要,JVM設置了4個常用的32位寄存器:pc(程序計數器)、optop(操作數棧頂指針)、frame(當前執行環境指針)和vars(指向當前執行環境中第一個局部變量的指針)。 

◆JVM棧:指令執行時數據和信息存儲的場所和控制中心,它提供給JVM解釋器運算所需要的信息。當JVM得到一個Java字節碼應用程序後,便爲該代碼中一個類的每一個方法創建一個棧框架,以保存該方法的狀態信息。每個棧框架包括以下三類信息:局部變量、執行環境、操作數棧。

  局部變量用於存儲一個類的方法中所用到的局部變量。vars寄存器指向該變量表中的第一個局部變量。

  執行環境用於保存解釋器對Java字節碼進行解釋過程中所需的信息。它們是:上次調用的方法、局部變量指針和操作數棧的棧頂和棧底指針。執行環境是一個執行一個方法的控制中心。例如:如果解釋器要執行iadd(整數加法),首先要從frame寄存器中找到當前執行環境,而後便從執行環境中找到操作數棧,從棧頂彈出兩個整數進行加法運算,最後將結果壓入棧頂。

  操作數棧用於存儲運算所需操作數及運算的結果。 

◆存儲區:JVM有兩類存儲區:常量緩衝池和方法區。常量緩衝池用於存儲類名稱、方法和字段名稱以及串常量。方法區則用於存儲Java方法的字節碼。 

◆碎片回收區:JVM碎片回收是指將使用過的Java類的具體實例從內存進行回收,這就使得開發人員免去了自己編程控制內存的麻煩和危險。隨着JVM的不斷升級,其碎片回收的技術和算法也更加合理。JVM 1.4.1版後產生了一種叫分代收集技術,簡單來說就是利用對象在程序中生存的時間劃分成代,以此爲標準進行碎片回收。 

2.JAVA的垃圾回收機制GC通過確定對象是否被活動對象引用來確定是否收集該對象。

2.1 觸發GC(Garbage Collector)的條件

1)GC在優先級最低的線程中運行,一般在應用程序空閒即沒有應用線程在運行時被調用。但下面的條件例外。

2)Java堆內存不足時,GC會被調用。當應用線程在運行,並在運行過程中創建新對象,若這時內存空間不足,JVM就會強制調用GC線程。若GC一次之後仍不能滿足內存分配,JVM會再進行兩次GC,若仍無法滿足要求,則JVM將報“out of memory”的錯誤,Java應用將停止。 

2.2 兩個重要方法

2.2.1 System.gc()方法

    使用System.gc()可以不管JVM使用的是哪一種垃圾回收的算法,都可以請求Java的垃圾回收。在命令行中有一個參數-verbosegc可以查看Java使用的堆內存的情況,它的格式如下:java -verbosegc classfile    由於這種方法會影響系統性能,不推薦使用,所以不詳訴。 

2.2.2 finalize()方法

    在JVM垃圾回收器收集一個對象之前,一般要求程序調用適當的方法釋放資源,但在沒有明確釋放資源的情況下,Java提供了缺省機制來終止該對象心釋放資源,這個方法就是finalize()。它的原型爲:protected void finalize() throws Throwable   在finalize()方法返回之後,對象消失,垃圾收集開始執行。原型中的throws Throwable表示它可以拋出任何類型的異常。之所以要使用finalize(),是存在着垃圾回收器不能處理的特殊情況。例如:1)由於在分配內存的時候可能採用了類似 C語言的做法,而非JAVA的通常new做法。這種情況主要發生在native method中,比如native method調用了C/C++方法malloc()函數系列來分配存儲空間,但是除非調用free()函數,否則這些內存空間將不會得到釋放,那麼這個時候就可能造成內存泄漏。但是由於free()方法是在C/C++中的函數,所以finalize()中可以用本地方法來調用它。以釋放這些“特殊”的內存空間。2)又或者打開的文件資源,這些資源不屬於垃圾回收器的回收範圍。

2.3 減少GC開銷的措施

1)不要顯式調用System.gc()。此函數建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數。大大的影響系統性能。

 2)儘量減少臨時對象的使用。臨時對象在跳出函數調用後,會成爲垃圾,少用臨時變量就相當於減少了垃圾的產生,從而延長了出現上述第二個觸發條件出現的時間,減少了主GC的機會。

3)對象不用時最好顯式置爲Null。一般而言,爲Null的對象都會被作爲垃圾處理,所以將不用的對象顯式地設爲Null,有利於GC收集器判定垃圾,從而提高了GC的效率。

4)儘量使用StringBuffer,而不用String來累加字符串。由於String是固定長的字符串對象,累加String對象時,並非在一個String對象中擴增,而是重新創建新的String對象,如Str5=Str1+Str2+Str3+Str4,這條語句執行過程中會產生多個垃圾對象,因爲對次作“+”操作時都必須創建新的String對象,但這些過渡對象對系統來說是沒有實際意義的,只會增加更多的垃圾。避免這種情況可以改用StringBuffer來累加字符串,因StringBuffer是可變長的,它在原有基礎上進行擴增,不會產生中間對象。

5)能用基本類型如Int,Long,就不用Integer,Long對象。基本類型變量佔用的內存資源比相應對象佔用的少得多,如果沒有必要,最好使用基本變量。

6)儘量少用靜態對象變量。靜態變量屬於全局變量,不會被GC回收,它們會一直佔用內存。

7)分散對象創建或刪除的時間。集中在短時間內大量創建新對象,特別是大對象,會導致突然需要大量內存,JVM在面臨這種情況時,只能進行主GC,以回收內存或整合內存碎片,從而增加主GC的頻率。集中刪除對象,道理也是一樣的。它使得突然出現了大量的垃圾對象,空閒空間必然減少,從而大大增加了下一次創建新對象時強制主GC的機會。  

2.4 對象在JVM堆區的狀態

1)可觸及狀態:程序中還有變量引用,那麼此對象爲可觸及狀態。

2)可復活狀態:當程序中已經沒有變量引用這個對象,那麼此對象由可觸及狀態轉爲可復活狀態。CG線程將在一定的時間準備調用此對象的finalize方法(finalize方法繼承或重寫子Object),finalize方法內的代碼有可能將對象轉爲可觸及狀態,否則對象轉化爲不可觸及狀態。

3)不可觸及狀態:只有當對象處於不可觸及狀態時,GC線程才能回收此對象的內存。

 

Jvm堆區對象狀態轉換圖 

2.5 常用垃圾收集器

1) 標記-清除收集器 Mark-Sweep

2) 複製收集器        Copying

3) 標記-壓縮收集器 Mark-Compact

4) 分代收集器   Generational 

2.6 垃圾收集算法介紹

2.6.1  tracing算法

    基於tracing算法的垃圾收集也稱爲標記和清除(mark-and-sweep)垃圾收集器.

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

 

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

2.6.2 Copying算法

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

 

這種算法雖然實現簡單,運行高效且不容易產生內存碎片,但是卻對內存空間的使用做出了高昂的代價,因爲能夠使用的內存縮減到原來的一半。很顯然,Copying算法的效率跟存活對象的數目多少有很大的關係,如果存活對象很多,那麼Copying算法的效率將會大大降低。

2.6.3 compacting算法

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

 

2.6.4 Generation算法 

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

  目前大部分垃圾收集器對於新生代都採取Copying算法,因爲新生代中每次垃圾回收都要回收大部分對象,也就是說需要複製的操作次數較少,但是實際中並不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另一塊Survivor空間中,然後清理掉Eden和剛纔使用過的Survivor空間。而由於老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact算法。

 

新年代:新創建的對象都存放在這裏。因爲大多數對象很快變得不可達,所以大多數對象在年輕代中創建,然後消失。當對象從這塊內存區域消失時,我們說發生了一次“minor GC”。

  老年代:沒有變得不可達,存活下來的年輕代對象被複制到這裏。這塊內存區域一般大於年輕代。因爲它更大的規模,GC發生的次數比在年輕代的少。對象從老年代消失時,我們說“major GC”(或“full GC”)發生了。

 上圖中的永久代(permanent generation)也稱爲“方法區(method area)”,他存儲class對象和字符串常量。所以這塊內存區域絕對不是永久的存放從老年代存活下來的對象的。在這塊內存中有可能發生垃圾回收。發生在這裏垃圾回收也被稱爲major GC。

  對於分代算法,我推薦一篇博客給大家:http://blog.jobbole.com/80499/



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章