1.垃圾回收概述
隨着程序的不斷運行,程序所產生的對象必將越來越多,而系統的內存則是有限的,所以,將沒有用的對象進行清除是程序長期穩定運行的關鍵.
垃圾回收主要關注三個問題
-
什么對象應該被回收?
當然是沒有用的對象.當對象不再被引用時,我們認為該對象應該被回收.如何判斷對象是否還被引用,會在后面詳述. -
對象應該在什么時間被回收?
程序在運行過程中,對象的引用關系是一直變化的,如何選擇合適的時機開始GC,也是一個重要的問題,后面詳述. -
應該怎樣回收?
當我們知道是無用對象后,如何將無用對象清除,保留有用對象,將是垃圾回收算法主要關注的問題,將在下一節詳述.
2.判斷對象是否被引用
主要有兩種辦法判斷對象是否被引用.
- 引用計數器法
顧名思義,為每個對象定義一個計數器,每當對象被引用一次時,程序計數器+1,當引用被銷毀一次,計數器-1,如果計數器值為0的時,則對象無用.
此方法有一個缺點,就是無法解決對象之間的相互引用問題.
如:objA.instance = objB; objB.instance = objA;
則這兩個對象將永遠無法回收.
- 可達性分析法
選擇一些根節點(GC Roots)對象,向下進行引用查找,查找走過的路徑則稱為引用鏈,如果一個對象沒有一條引用鏈可以達到它,則它為無用對象.如圖所示.
如GCRoot引用到了A對象。
A對象引用到了B對象。
則A、B對象都不可以回收。
3.選擇合適的時間開始GC
- 由於程序運行過程中,對象的引用關系一直在發生變化,所以我們需要等到所有線程停止執行(stop the world)才能開始進行.
- JVM准備GC之前,會將一個標志位設置為真,每個線程都會去檢查這個標志位,如果發現為真,則停止往下執行,並將當前線程棧中引用(局部變量)的對象作為GC Roots,向下查找引用鏈,並存入一個oopMap中.
注:哪些對象可以作為GC Roots?
- 靜態變量
- 常量
- 線程棧中引用(局部變量)引用的對象
- 如果線程每執行一條指令就去檢查標志位,顯然太過浪費,此時就讓線程多走幾步到某些位置上,再開始進行可達性分析,這些位置稱為安全點.
注:一般將哪些位置作為安全點?
- 方法return之后
- 循環單次結束后
- 發生異常准備跳轉catch前.
- 當線程正在sleep()時,引用關系是不會發生變化的,這段時間稱為安全區域.線程進入安全區域和走到安全點效果是一樣的.當線程sleep()結束后,會先通過標志位判斷GC是否結束,沒有結束則會繼續等待.
4.其他
- 對象不可達時,如何避免被回收?
實現finalize()后,有且僅有一次執行finalize()以逃脫回收的機會,但一般不建議使用.
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("對象仍然活着!!!");
}
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize()方法被執行!!!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable {
new FinalizeEscapeGC(); // 對象被創建
SAVE_HOOK = null;
System.gc(); // 回收,會執行finalize()
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("對象已死!!!");
}
SAVE_HOOK = null;
System.gc(); // 回收,finalize()已經被執行過,不會再被執行
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("對象已死!!!");
}
}
}
finalize()方法被執行!!!
對象仍然活着!!!
對象已死!!!
- 關於引用
如果reference類型的數據中存儲的是另一塊內存的地址,則次reference對象為一個引用.
按照強弱程度,引用可以分為強引用,軟引用,弱引用,虛引用.
1.強引用
Object obj = new Object();
除非棧幀清空,強引用不會被GC,即使發生OOM.
2.軟引用
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
sf.get();//有時候會返回null
軟引用在發生內存不足時會被回收,內存足夠時何以通過get()獲取.
軟引用通常被用於實現緩存.
3.弱引用
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有時候會返回null
wf.isEnQueued();//返回是否被垃圾回收器標記為即將回收
弱引用是在第二次垃圾回收時回收,即使內存足夠.短時間內通過弱引用取對應的數據,可以取到,當執行過第二次垃圾回收時,將返回null
4.虛引用
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永遠返回null
pf.isEnQueued();//返回是否從內存中已經刪除
在垃圾回收時,虛引用一定會被回收,就和沒有引用一樣.
- 方法區的垃圾回收
垃圾回收的主要區域是堆,但方法區一樣也會發生垃圾回收.一次對年輕代的回收可以回收70%-95%的空間,但永久代的回收效率要低很多.
永久代的垃圾回收主要針對廢棄常量和廢棄類.
廢棄常量
如"abc"如果沒有任何引用,則會被回收.
廢棄類
對廢棄類的回收需要滿足一下三個條件:
1.該類的所有實例已被回收
2.該類的ClassLoader已被回收
3.該類對應的java.lang.Class對象無任何地方被引用
注:可以通過參數-Xnoclassgc,設置是否對類進行回收.
在大量使用反射,動態代理,CGLib等ByteCode框架, 動態生成JSP以及OSGi這類頻繁
自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證永久代不會溢出。