什麼情況下Java物件才是已經死亡?
已死的物件就是不可能被任何途徑使用的物件,有以下幾種方法判斷一個物件是否已經死了;
引用計數
給物件新增一個引用計數器,每當有一個地方引用他,計算器就加1;當引用失效時,計數器減1;任何時刻計數器為0的物件就是死的物件。
1. 這種方式被很多技術所採用,如FlashPlayer(AS3)、Python等,但是Java沒有采用這種演算法,原因是它很難解決物件之間相互迴圈引用的問題,例如 ObjectA.param=ObjectB,ObjectB.param=ObjectA,物件A和B相互引用但是除此之外他們再無任何其他引用,這樣他們的引用計數都不為0,永遠不會被回收
根搜尋
java採用的是根搜尋演算法,這個演算法的基本思路是通過一系列名為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所經過的路徑稱為引用鏈,當GC Roots到一個物件不可達時,這個物件就是不可用的
1. Java中可作為GC Roots的物件包括:
1)虛擬機器棧中引用的物件
2)方法區中類靜態屬性和常量引用的物件
3)本地方法棧中Native方法引用的物件
引用方式
簡單描述引用就是一塊記憶體中儲存的數值代表的是另外一塊記憶體的起始地址,則成這塊記憶體代表著一個引用,這種描述很純粹但是是不太正確的,可以作為理解時用
1. 引用分為強引用、軟引用、弱引用、虛引用
1)強引用:如Object obj=new Object();只要強引用存在,GC肯定不會回收被引用的物件
2)軟引用:非必需的物件,當系統要發生記憶體溢位之前,會把這些物件列入回收範圍
3)弱引用:更加非必需的物件,弱引用關聯的物件只能生存到下一次垃圾回收之前
4)虛引用:也叫幽靈引用或者幻影引用,它是最弱的一種引用,虛引用不會引用一個物件的生命週期,也無法通過一個虛引用獲取一個物件例項,只是虛引用的物件被回收時會有一個通知返回回去
兩次標記
即使上面的演算法已經判定這個物件“非死不可”,但是他們也只是出於死緩階段,要真正判定他死亡,至少要經歷兩次標記過程;如果物件在第一個進行可達性分析後發現沒有與GC ROOTS相連線的引用鏈,那麼它將會被第一次標記並且進行一次篩選,篩選的條件是此物件是否有必要執行finalize()方法。如果這個物件沒有覆蓋finalize()方法,或者finalize已經被呼叫過,那麼這個物件才會被正在的執行死刑。 首先第一次這個物件被判定為有必要執行finalize()方法,那麼這個物件會被放置在一個叫做F-Queue的佇列之後,之後會被一個低優先順序的小執行緒執行。finalize()方法是物件逃脫死亡命運的最後一次機會,稍後GC將會對F-queue重的物件進行第二次標記,如果物件要在finalize()中拯救自己需要重新與引用鏈上的任何一個物件建立關聯,如果在第二次標記前,這個物件還沒有移除即將回收的集合,那麼它基本上就要被回收了。 [java]- publicclass FinalizeEscapeGc {
- publicstatic FinalizeEscapeGc SAVE_HOOK=null;
- publicvoid isAlive() {
- System.out.println(”yes,i am still alive;”);
- }
- @Override
- protectedvoid finalize() throws Throwable {
- // TODO Auto-generated method stub
- super.finalize();
- System.out.println(”finalize method executed!”);
- FinalizeEscapeGc.SAVE_HOOK=this;
- }
- publicstaticvoid main(String[] args) throws InterruptedException {
- SAVE_HOOK=new FinalizeEscapeGc();
- SAVE_HOOK=null;
- System.gc();
- //因為finalize方法優先順序很低,所以暫停0.5秒以等待它
- Thread.sleep(500);
- if(SAVE_HOOK!=null) {
- SAVE_HOOK.isAlive();
- }else {
- System.out.println(”no,i am dead;”);
- }
- SAVE_HOOK=null;
- System.gc();
- //因為finalize方法優先順序很低,所以暫停0.5秒以等待它
- Thread.sleep(500);
- if(SAVE_HOOK!=null) {
- SAVE_HOOK.isAlive();
- }else {
- System.out.println(”no,i am dead;”);
- }
- }
- }
public class FinalizeEscapeGc {
public static FinalizeEscapeGc SAVE_HOOK=null;
public void isAlive() {
System.out.println("yes,i am still alive;");
}
@Override
protected void finalize() throws Throwable {
// TODO Auto-generated method stub
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGc.SAVE_HOOK=this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK=new FinalizeEscapeGc();
SAVE_HOOK=null;
System.gc();
//因為finalize方法優先順序很低,所以暫停0.5秒以等待它
Thread.sleep(500);
if(SAVE_HOOK!=null) {
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead;");
}
SAVE_HOOK=null;
System.gc();
//因為finalize方法優先順序很低,所以暫停0.5秒以等待它
Thread.sleep(500);
if(SAVE_HOOK!=null) {
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead;");
}
}
}
finalize簡單來說就是當垃圾回收器認為一個物件沒有存在意義時,會呼叫該物件的這個方法,釋放該物件在堆中佔用的記憶體。此方法繼承於始祖Object。
回收方法區
JVM規範中說過方法區(永久代)可以不實現垃圾回收,但是被上了免死金牌的永久代也是可以殺的,只是殺的少些而已,不像新生代,一次垃圾收集就可以殺掉70%~95%的物件
1.該類所有的例項都已經被回收,也就是Java堆中不存在該類的任何例項
2.載入該類的ClassLoader已經被回收
3.該類對應的java.lang.Class物件沒有在任何地方被引用,無法再任何地方通過反射訪問該類的方法
在大佬使用反射,動態代理,CGLib等ByteCode框架,動態生成JSP以及OSGI這類頻繁自定義ClassLoader的場景都需要虛擬機器具備類解除安裝的功能,以保證永久代不會溢位。