1. 程式人生 > >Java GC算法

Java GC算法

情況 tails 進一步 nbsp 靜態變量 對象賦值 原理 常見 缺點

轉自:http://blog.csdn.net/heyutao007/article/details/38151581

1、JVM內存組成結構


JVM內存結構由堆、棧、本地方法棧、方法區等部分組成,結構圖如下所示:

技術分享



1)堆


所有通過new創建的對象的內存都在堆中分配,其大小可以通過-Xmx和-Xms來控制。堆被劃分為新生代和舊生代,新生代又被進一步劃分為Eden和Survivor區,Survivor由FromSpace和ToSpace組成,結構圖如下所示:


技術分享

新生代。新建的對象都是用新生代分配內存,Eden空間不足的時候,會把存活的對象轉移到Survivor中,新生代大小可以由-Xmn來控制,也可以用-XX:SurvivorRatio來控制Eden和Survivor的比例。


舊生代用於存放新生代中經過多次垃圾回收(也即Minor GC)仍然存活的對象


2)棧


每個線程執行每個方法的時候都會在棧中申請一個棧幀,每個棧幀包括局部變量區和操作數棧,用於存放此次方法調用過程中的臨時變量、參數和中間結果


3)本地方法棧


用於支持native方法的執行,存儲了每個native方法調用的狀態


4)方法區


存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(或者叫做永久代,PermanetGeneration)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。

在過去(自定義類加載器還不是很常見的時候),類大多是”static”的,很少被卸載或收集,因此被稱為“永久的(Permanent)”。同時,由於類class是JVM實現的一部分,並不是由應用創建的,所以又被認為是“非堆(non-heap)”內存。


在JDK8之前的HotSpot JVM,存放這些”永久的”的區域叫做“永久代(permanent generation)”。永久代是一片連續的堆空間,在JVM啟動之前通過在命令行設置參數-XX:MaxPermSize來設定永久代最大可分配的內存空間,默認大小是64M(64位JVM由於指針膨脹,默認是85M)。永久代的垃圾收集是和老年代(old generation)捆綁在一起的,因此無論誰滿了,都會觸發永久代和老年代的垃圾收集。不過,一個明顯的問題是,當JVM加載的類信息容量超過了參數-XX:MaxPermSize設定的值時,應用將會報OOM的錯誤。

永久代在JDK8中被完全的移除了。所以永久代的參數-XX:PermSize和-XX:MaxPermSize也被移除了。

在JDK8中,classe metadata(the virtual machines internal presentation of Java class),被存儲在叫做Metaspace的native memory。一些新的flags被加入:
-XX:MetaspaceSize,class metadata的初始空間配額,以bytes為單位,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當的降低該值;如果釋放了很少的空間,那麽在不超過MaxMetaspaceSize(如果設置了的話),適當的提高該值。
-XX:MaxMetaspaceSize,可以為class metadata分配的最大空間。默認是沒有限制的。
-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩余空間容量的百分比,減少為class metadata分配空間導致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩余空間容量的百分比,減少為class metadata釋放空間導致的垃圾收集

默認情況下,class metadata的分配僅受限於可用的native memory總量。可以使用MaxMetaspaceSize來限制可為class metadata分配的最大內存。當class metadata的使用的內存達到MetaspaceSize(32位clientVM默認12Mbytes,32位ServerVM默認是16Mbytes)時就會對死亡的類加載器和類進行垃圾收集。設置MetaspaceSize為一個較高的值可以推遲垃圾收集的發生。

介紹完了JVM內存組成結構,下面我們再來看一下JVM垃圾回收機制。



2、java GC基本算法

2.1、引用計數(reference counting)
原理:此對象有一個引用,則+1;刪除一個引用,則-1。只用收集計數為0的對象。
缺點: (1)無法處理循環引用的問題。如:對象A和B分別有字段b、a,令A.b=B和B.a=A,除此之外這2個對象再無任何引用,那實際上這2個對象已經不可能再被訪問,但是引用計數算法卻無法回收他們。(2)引用計數的方法需要編譯器的配合。編譯器需要為此對象生成額外的代碼。如賦值函數將此對象賦值給一個引用時,需要增加此對象的引用計數。還有就是,當一個引用變量的生命周期結束時,需要更新此對象的引用計數器。

引用計數的方法由於存在顯著的缺點,實際上並未被JVM所使用。


2.2、復制(copying)
原理:把內存空間劃分為2個相等的區域,每次只使用一個區域。垃圾回收時,遍歷當前使用區域,把正在使用的對象復制到另外一個區域。
優點:不會出現碎片問題。
缺點:
(1)暫停整個應用。
(2)需要2倍的內存空間。


2.3、標記-清掃(Mark-and-sweep)
原理:對於“活”的對象,一定可以追溯到其存活在堆棧、靜態存儲區之中的引用。這個引用鏈條可能會穿過數個對象層次,算法基於有向圖,采用深度優先搜索。
第一階段:從GC roots開始遍歷所有的引用,對有活的對象進行標記。
第二階段:對堆進行遍歷,把未標記的對象進行清除。
優點:解決了循環引用的問題。
缺點:
(1)暫停整個應用;
(2)會產生內存碎片。
(3)不管你這個對象是不是可達的,即是不是垃圾,都要在清楚階段被檢查一遍,非常耗時.

sun前期版本就是用這個技術。


2.4、標記-壓縮(Mark-Compact)
第一階段:同標記-清掃,標記活的對象,
第二階段:這個階段將所有做了標記的活動對象整理到堆的底部
優點:
(1)避免標記掃描的碎片問題;
(2)避免停止復制的空間問題。


2.5、分代(generational collecting)
原理:基於對象生命周期分析得出的垃圾回收算法。把對象分為年輕代、年老代、持久代,對不同的生命周期使用不同的算法(2-3方法中的一個即4自適應)進行回收。

J2SE1.2以後使用此算法

JVM分別對新生代和舊生代采用不同的垃圾回收機制


2.5.1、新生代的GC(Minor GC):


指發生在新生代的垃圾收集動作,因為 Java 對象大多都具備朝生夕滅的特性,所以 Minor GC 非常頻繁,一般回收速度也比較快。


新生代通常存活時間較短,因此基於Copying算法來進行回收,所謂Copying算法就是掃描出存活的對象,並復制到一塊新的完全未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace之間copy。新生代采用空閑指針的方式來控制GC觸發,指針保持最後一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從eden到survivor,最後到舊生代,




2.5.2、舊生代的GC(Major GC / Full GC):


指發生在老年代的 GC。舊生代與新生代不同,對象存活的時間比較長,比較穩定,因此采用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,然後再進行回收未被標記的對象,回收後對用空出的空間要麽進行合並,要麽標記出來便於下次進行分配,總之就是要減少內存碎片帶來的效率損耗。


MajorGC 的速度一般會比 Minor GC 慢 10倍以上。


Thinking in java給java gc取了一個羅嗦的稱呼:“自適應、分代的、停止-復制、標記-掃描”式的垃圾回收器。


導致Gc的情況:
1、tenured被寫滿
2、perm被寫滿
3、System.gc()的顯式調用。
4、上一次GC之後heap的各域分配策略動態變化。

Java GC算法