jvm垃圾收集器與內存分配策略
垃圾收集器與內存分配策略:
- 以下參考周志明的<<深入理解jvm高級特性與最佳實踐>>。
判斷對象是否存活:
- 引用計數:通過判斷對象被引用的次數(為0,則表示不可被使用),但這很難解決對象相互循環引用的問題。
- 根搜索算法:即采用有向圖的方式,判斷從GC Roots到某個對象是否可達。
上圖中obj1,obj2,obj3,obj4可達,obj5,obj6不可達(可被GC回收)。
- 可作為GC Roots的對象:
1. 虛擬機棧中局部變量引用的對象。
2. 類靜態屬性引用的對象。
3. 常量引用的對象。
4. JNI中引用的對象。
java中的引用類型:
java中有四種引用類型(由強到弱):
- 強引用(StrongReference): 只要對象有被強引用,對象就不會被GC回收,如Object o = new Object()中的o就是強引用。
- 軟引用(SoftReference): 軟引用的對象為不必須回收的對象,但在內存不足發生溢出之前,GC會在回收時,將軟引用的對象視作可可回收的對象,所以SoftReference可以用來做一些高速緩存。
- 弱引用(WeakReference):軟引用的對象只能存活到下一次GC的時候,無論內存是否足夠,如WeakHashMap類。
- 虛引用(PhantomReference): 虛引用對對象存活無影響,唯一的用處,可以與ReferenceQueue結合使用,在對象被回收時,得以通知。
方法區(Permanent Generation)回收:
- java8中已經移除了Permanent Generation, 取而代之的是MetaSpace區。
- 方法區回收的對象:
1. 廢棄常量(如無引用的常量字符串)。
2. 無用的類。
- 類什么條件下可以被回收:
1. 該類的所有實例已經被回收。
2. 加載該類的ClassLoader已經被回收。
3. 該類的java.lang.Class對象沒有被引用,無法任何地方通過反射訪問該類。
垃圾回收算法:
- 標記-清除(Mark-Sweep): 先標記要回收的對象,再統一進行回收操作。
簡單方便,內存碎片化嚴重
- 復制算法: 將堆分為2份,1份空白(A),1份存放對象(B),GC后,將B中存活對象復制到A中,然后將B變成空白。
無內存碎片化, 浪費可用內存
- 標記-整理(Mark-Compact): 先標記可回收對象,將存活對象都移向一端,再回收對象。
無內存碎片化,充分利用可用內存
- 分代回收:將堆分為年輕代和老年代,年輕代(存活對象少)采用復制算法,老年代(對象存活長)可采用標記-清除或標記-整理。
根據對象存活特性,合理使用不同回收算法,商業JVM都使用該回收算法。
垃圾收集器:
- 既然jvm采用分代回收,那么年輕代和老年代各自使用的垃圾收集器也不一樣。
- 年輕代使用的垃圾收集器:Serial,ParNew,Parallel Scavenge。
- 老年代使用的垃圾收集器:CMS, Serial Old(MSC), Parallel Old。
- 通用的垃圾收集器:G1。
Serial/Serial Old收集器:
- 單線程回收,回收期間暫停其他所有工作線程。(jvm -client模式下年輕代默認的收集器)
ParNew收集器:
- 多線程同時回收,回收期間暫停其他所有工作線程。
Parallel Scavenge收集器:
- 和ParNew類似,但其重在控制cpu吞吐量大小。
Serial Old收集器:
- Serial老年代版本。使用單線程,標記-整理的算法。
Parallel Old收集器:
- Parallel Scavenge收集器的老年代版本。使用多線程,標記-整理算法。
CMS(Concurrent Mark Sweep)收集器:
- 以獲得最短回收暫停時間為目標。這是一款真正意義的并發收集器(可允許垃圾收集線程與用戶線程并行)。其主要有四個階段:
1. 初始標記:標記GC Roots可關聯的對象,會暫停用戶線程。
2. 并發標記:GC Roots Tracing過程,用戶線程并行。
3. 重新標記:重新標記由于在并發標記過程中用戶線程導致的對象狀態變化,會暫停用戶線程。
4. 并發清理:清理可回收對象,用戶線程并行。
G1(Garbage First)收集器:
- 一款先進的垃圾收集器。它針對整個堆,將其分為大小相等的區域,記錄每個區占用信息,優先回收回收價值更高的區域。
- 詳解介紹:http://my.oschina.net/indestiny/blog/214160
這些收集器都一些可控參數,根據實際場景來調整,這里是官方文檔:
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
內存分配與回收策略:
- jvm內存分配基本就是堆上分配,也有可能棧上(如通過逃逸分析,可以在棧上分配)。
- 看看整個堆分區:
對象優先分配在Eden區:
內存分配回收實例:
/**
- 內存分配回收
- -XX:+PrintGCDetails 打印GC信息
- -Xms12M 起始堆大小
- -Xmx12M 最大堆大小
- -Xmn6M 年輕代大小
- -XX:SurvivorRatio=6 Eden:S0:S1 = 4:1:1
- jvm args:
-XX:+PrintGCDetails -Xms12M -Xmx12M -Xmn6M -XX:SurvivorRatio=4 / public class MemoryAllocate { private static final int _1M = 10241024;
public static void main(String[] args) {
byte[] b1 = new byte[_1M]; // allocate 1M byte[] b2 = new byte[_1M*2]; // allocate 2M byte[] b3 = new byte[_1M]; // allocate 1M, 發生年輕代GC(Minor GC)
} }</pre> gc信息如下:
- 垃圾回收方式:
1.Minor GC:發生在年輕代,速度快,頻繁。
2.Full GC/Major GC:發生在老年代, 一般也伴隨Minor GC, 速度一般比Minor GC慢10倍以上。
大對象直接進入老年代:
- 可以通過設定-XX:PretenureSizeThreshold來判斷當分配的對象大于這個閾值時,則直接將其分配到老年代。
- PretenureSizeThreshold只對Serial和ParNew收集器有效。
/**
- PretenureSizeThreshold
- -XX:+PrintGCDetails 打印GC信息
- -Xms12M 起始堆大小
- -Xmx12M 最大堆大小
- -Xmn6M 年輕代大小
- -XX:SurvivorRatio=6 Eden:S0:S1 = 4:1:1
- -XX:PretenureSizeThreshold 對象超過該值,直接進入老年代,單位B
- -XX:UseParNewGC 使用UserParNew收集器
- jvm args:
-XX:UseParNewGC -XX:+PrintGCDetails -Xms12M -Xmx12M -Xmn6M -XX:SurvivorRatio=4 -XX:PretenureSizeThreshold=2097152 / public class PretenureSizeThreshold { private static final int _1M = 10241024;
public static void main(String[] args) {
// 分配2M, 大于PretenureSizeThreshold, 直接進入老年代 byte[] b1 = new byte[_1M * 3];
} }</pre>
其他參數介紹:
- MaxTenuringThreshold: 在Eden區,對象在每次Minor GC后存活下來,則其年齡+1, 當存活對象年齡大于MaxTenuringThreshold時,就進入老年代。
- HandlePromotionFailure: 空間擔保機制。在發生Minor GC時,虛擬機會檢測比較每次晉升到老年代的平均大小與老年代剩下的空間大小,如果大于,則進行一次Full GC;如果小于,則查看HandlePromotionFailure是否允許擔保失敗,若允許只進行Minor GC;若不允許,還是會進行一次Full GC。
上面就基本介紹了jvm分配回收內存策略。
不吝指正。