1. 概述
GC需要完成的事情
1、哪些內存需要回收
2、什麼時候回收
3、如何回收
2. 對象已死
2.1 引用計數算法
給對象中添加一個引用計算器,每當有一個地方引用它時,計數器加一,當引用失效時,計數器減一
。任何時刻都爲0的對象就是不可能再被使用了。
2.2 根搜索算法
通過一系列的名爲“GC Root”的對象起始點,從這些節點開始向下搜索,搜索所有走過的路徑稱爲引用鏈。當一個對象到“GC Root”沒有任何引用鏈相連。則證明對象不可用。
GC root對象包括如下
虛擬棧(棧中的引用變量表)中的引用的對象
方法區中的類靜態屬性引用的對象
方法區的常量引用的對象
本地方法棧中的JNI(native)方法
2.3 再談引用
強引用:只要強引用存在,垃圾回收器永遠不會回收掉被原因的對象
軟引用:在系統發將要發生內存溢出溢出之前,將會把這些對象列進回收範圍之中進行第二次回收
弱引用:被關聯的對象只能生存到下一次垃圾收集發生之前
虛引用:一個對象是否 有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛yiny取得一個對象實例
2.4 存在還是死亡
對象的死亡至少經過兩次標記
第一次:對象從根搜索發現沒有與GC Roots相連接的引用鏈,那它會被第一次標記並且進行一次篩選,篩選條件爲對象是否有必要執行finalize()。
第二次:對象執行了finalize()方法,那麼這個對象會被放置一個名爲F-Queue的隊列之中。
代碼清單:
package com.one.jvm;
/**
* 測試對象只調用一次Finalize()方法
* @author Administrator
*
*/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC escapeGC = null;
@Override
protected void finalize() throws Throwable {
// TODO Auto-generated method stub
super.finalize();
System.out.println("finalize method excuted");
escapeGC = this;
}
public static void main(String[] args) throws InterruptedException {
escapeGC = new FinalizeEscapeGC();
escapeGC = null;
//運行垃圾回收器。
System.gc();
Thread.sleep(600);
if(escapeGC !=null) {
System.out.println("escapeGC Object not null");
} else {
System.out.println("escapeGC Object is null");
}
escapeGC = null;
//運行垃圾回收器。
System.gc();
Thread.sleep(600);
if(escapeGC !=null) {
System.out.println("escapeGC Object not null");
} else {
System.out.println("escapeGC Object is null");
}
}
}
2.5 方法區回收
類需要同時滿足下面三個條件才能算無用的類
1、該類所有的實例已經被回收,也就是Java堆中不存在該類的任何實例
2、加載該類的ClassLoder已經被回收
3、該類對應的java.lang.Class對象沒有在任何地方被引用,無 法通過任何地方通過反射訪問該類的方法
3. 垃圾收集算法
1、標記-清除算法(標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象)
缺點:1、標識與清除是效率不高,2、會產生大量不連續內存碎片
2、複製算法:他將可用的內存按容量劃分大小相等的兩塊,每次只使用其中一塊。當這塊的內存用完了,就將存活的對象複製到另外一塊上面,然後再把已使用過得內存空間一次清理。(新生代比較適合)
3、標記-整理算法:左右存活的對象都往一遍移,然後直接清理掉邊界以外的內存(老年代比較適合)
4. 垃圾收集器
垃圾收集器是內存回收的具體實現
5. 內存分配與回收策略
5.1 對象優先在Eden分配
對象在新生代Eden區中分配內存,當Eden去沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC
package com.one.jvm;
/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* 參數解釋
* -Xms20M:設置JVM促使內存爲10M。此值可以設置與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配內存。
* -Xmx20M :設置JVM最大可用內存爲20M
* -Xmn10M :設置年輕代大小爲10M。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小爲64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
* -XX:PrintGCDetails,開啓詳細GC日誌模式
* -XX:SurvivorRatio=8:年輕代中Eden區與兩個Survivor區的比值(8:1)
* @author Administrator
*
*/
public class EdenAllocation {
private static final int _1MB = 1024*1024;
public static void main(String[] args) {
byte[] eden4 = new byte[2*_1MB];
byte[] eden5 = new byte[2*_1MB];
byte[] eden6 = new byte[2*_1MB];
//新生代垃圾回收
byte[] eden7 = new byte[2*_1MB];
}
}
日誌
[GC [PSYoungGen: 6495K->304K(9216K)] 6495K->6448K(19456K), 0.0091599 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 304K->0K(9216K)] [PSOldGen: 6144K->6328K(10240K)] 6448K->6328K(19456K) [PSPermGen: 2967K->2967K(21248K)], 0.0069367 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 2375K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 29% used [0x00000000ff600000,0x00000000ff851f98,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
PSOldGen total 10240K, used 6328K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 61% used [0x00000000fec00000,0x00000000ff22e140,0x00000000ff600000)
PSPermGen total 21248K, used 2984K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)
object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9cea3a0,0x00000000faec0000)
日誌參數含義
第一行GC是指發生在新生代的垃圾收集動作,PSYoungGen表示Parallel Scavenge垃圾回收器,
6495K->304K(9216K):6495K表示新生代垃圾回收前的大小,304K表示新生代垃圾回收後的大小。9216K表示新生代的大小。6495K->6448K(19456K):6495K表示整個堆回收前的大小,6448K表示整個堆回收後的大小,19456K表示整個堆內存分配大小第二行Full GC指發生在老年代的垃圾收集動作,PSYoungGen表示Parallel
Scavenge垃圾回收器,304K->0K(9216K):304K表示新生代垃圾回收前的大小,0K表示新生代垃圾回收後的大小。 PSOldGen表示垃圾回收器,6144K->6328K(10240K):6144K表示老年代垃圾回收前的大小,6328K表示老年代垃圾回收後的大小。10240K表示整個老年代的大小。10240K表示整個老年代的大小。6448K->6328K(19456K):6448K表示整個堆回收前的大小,6328K表示整個堆回收的大小,19456K表示整個堆內存
GC內存走向分析
- 第一次新生代GC的內存走向分析
6495K->304K,新生代回收的內存大小(6191K=6495K-304K)。發出這樣的疑問?6191K到底去向何處。分析一下,
6495K->6448K,整個堆回收的大小(47K)=6495K -
6448K;剩下內存去那兒了(6191K-47K=6144K),哈哈,6144K去老年代了(有一段日誌:[PSOldGen:
6144K->6328K(10240K)]) - 第二次老年代GC的內存走向分析
304K->0K,在進行老年代GC時,首先把新生代的內存回收完畢。那麼會產生一個疑問,清理的304K去向何處?分析一下,6144K->6328K,老年代增加了184K,整個內存堆回收了(6448K->6328K)120K。184K+120K=304K。
5.2 大對象直接進入老年代
所謂的大對象是指,需要大量連續內存空間的java對象,
package com.one.jvm;
/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* 參數解釋
* -Xms20M:設置JVM促使內存爲10M。此值可以設置與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配內存。
* -Xmx20M :設置JVM最大可用內存爲20M
* -Xmn10M :設置年輕代大小爲10M。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小爲64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
* -XX:PrintGCDetails,開啓詳細GC日誌模式
* -XX:SurvivorRatio=8:年輕代中Eden區與兩個Survivor區的比值(8:1)
* @author Administrator
*
*/
public class EdenAllocation {
private static final int _1MB = 1024*1024;
public static void main(String[] args) {
byte[] eden1 = new byte[8*_1MB];
}
}
GC日誌:
Heap
PSYoungGen total 9216K, used 515K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 6% used [0x00000000ff600000,0x00000000ff680ee8,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
PSOldGen total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000)
PSPermGen total 21248K, used 2975K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)
object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9ce7e48,0x00000000faec0000)
長期存活的對象進入老年代
動態對象年齡判定
空間分配擔保