一、對象死了嗎
垃圾收集器在回收Java對象時,首先要確定對象是否存活。在虛擬機規範中,定義了兩種方式判斷對象的是否被回收。
1. 引用計數法(Reference Counting)
給對象添加一個引用計數器,當有地方引用到它時,計數器加1;當引用失效時,計數器減1;只要當計數器爲0時,該對象就不可能再被引用。
引用計數器法的實現比較簡單,判斷效率也很高,但是在主流的Java虛擬機裏面沒有選用來管理內存,其中最主要的原因是它很難解決對象之間相互循環的引用問題。
/**
* Created by ZRD on 2017/02/14.
* 測試引用計數法的對象回收現象
*/
public class ReferenceCountingGC {
/**
* VM Args: -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime
* @param args
*/
public static void main(String[] args) {
testGC();
}
public Object instance = null;
private static final int _1MB = 1 * 1024 * 1024; //1B*1024=1KB
private byte[] bigSize = new byte[_1MB];
public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
// 假設在這裏發生GC
System.gc();
}
}
2017-02-14T10:27:19.994+0800: [GC [PSYoungGen: 4669K->2712K(37888K)] 4669K->2712K(123904K), 0.0217407 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
2017-02-14T10:27:20.016+0800: [Full GC [PSYoungGen: 2712K->0K(37888K)] [ParOldGen: 0K->2577K(86016K)] 2712K->2577K(123904K) [PSPermGen: 2931K->2930K(21504K)], 0.0123248 secs] [Times: user=0.08 sys=0.00, real=0.01 secs]
2017-02-14T10:27:20.028+0800: Total time for which application threads were stopped: 0.0342405 seconds
Heap
PSYoungGen total 37888K, used 1638K [0x00000007d6000000, 0x00000007d8a00000, 0x0000000800000000)
eden space 32768K, 5% used [0x00000007d6000000,0x00000007d6199ac8,0x00000007d8000000)
from space 5120K, 0% used [0x00000007d8000000,0x00000007d8000000,0x00000007d8500000)
to space 5120K, 0% used [0x00000007d8500000,0x00000007d8500000,0x00000007d8a00000)
ParOldGen total 86016K, used 2577K [0x0000000782000000, 0x0000000787400000, 0x00000007d6000000)
object space 86016K, 2% used [0x0000000782000000,0x0000000782284580,0x0000000787400000)
PSPermGen total 21504K, used 2961K [0x000000077ce00000, 0x000000077e300000, 0x0000000782000000)
object space 21504K, 13% used [0x000000077ce00000,0x000000077d0e4608,0x000000077e300000)
4669K->2696K(37888K)上面的日誌信息中可以獲取到。說明這個時間段發生了垃圾回收,也就意味着虛擬機並沒有因爲這兩個對象相互引用而沒有回收,實際已經被回收了。
2. 可達性分析(Reachability Analysis)
可達性分析算法的思路是:以GC Roots爲起點,從這些節點向下搜索,所有走過的路徑稱爲引用鏈(Reference Chain);當一個對象沒有GC Roots的對象引用它時,該對象就會成爲垃圾收集器的目標
在Java語言中,GC Roots對象包括下面幾種:
1. Java棧中引用的對象(也就是棧幀中的本地變量表)
2. 方法區中的類靜態屬性引用的對象
3. 方法區中的常量引用的對象
4. Native方法引用的對象
如上圖,Object5,Object6,Object7會成爲垃圾收集器的目標
二、reference引用
在Java中,有4中引用類型,用來定義引用的強弱。方便的內存不夠時,逐步按等級定義回收的策略。
一些常見的緩存系統設計中比較常見
按引用的強弱程度分爲4類
1. 強引用
強引用在代碼中隨處可見。類似ReferenceCountingGC objA = new ReferenceCountingGC();這種就是強引用。
只要強引用還存在,垃圾收集器永遠不會回收。
2. 軟引用(SoftReference)
- 描述一些還有用,但非必須的對象。
在系統發生內存溢出之前,將會把這些對象列入回收範圍進行第二次回收。
如果回收還沒足夠的內存,則會發生內存溢出異常
3. 弱引用(WeakReference)
- 非必須的對象
- 只能存活到下次垃圾收集之前
當垃圾收集器開始工作時,無論內存是否足夠,都會回收掉這類對象
4. 虛引用(PhantomReference)
- 幽靈引用或者幻影引用
一個對象是否有虛引用,完全不會對其生存時間構成影響,也無法通過虛引用來獲取一個對象實例。
- 設置該類引用的唯一目的是這個對象被垃圾收集器回收時能夠收到一個系統通知
三、回收方法區
很多人認爲方法區是沒有垃圾收集的,Java虛擬機規範也說可以不要求在該區域實現垃圾收集,因爲回收效果不好。
方法區的回收內容:廢棄常量,無用的類
1. 回收廢棄常量
回收廢棄常量和回收Java堆中的對象非常相似,比如一個字符串,會放入常量池,當前系統中沒有任何String對象引用常量池中的字符串,如果這個時候發生內存回收,系統會把字符串清理出常量池。
2. 回收無用的類
判斷是否爲無用的類有幾個條件
* 該類的雖有對象均已被回收,也就是堆中不存在該類的任何對象
* 加載該類的ClassLoader已經被回收
* 該類對應的java.lang.Class對象沒有任何地方被引用,也沒有地方通過反射訪問該類
對象的finalize()方法
當對象沒有GC Roots引用鏈時,還沒這麼快被回收,對象會被第一次標記,並且還要做一個篩選判斷有沒有必要執行finalize(),篩選標準是對象沒有重寫finalize()或者已經被虛擬機調用過。
當判斷有必要執行時,放入一個F-Queue隊列中等待執行
如果在finalize()方法中將該對象重新產生一個GC Roots引用,則該對象成功逃過垃圾回收器的回收
但其實不鼓勵這種做法