1. 如何判斷對象可以被回收
堆中幾乎放著所有的對象實(shí)例,對堆垃圾回收前的第一步就是要判斷那些對象已經(jīng)死亡(即不能再被任何途徑使用的對象)。
1.1 引用計(jì)數(shù)法
實(shí)現(xiàn)思路:給對象添加一個引用計(jì)數(shù)器肮砾。每當(dāng)有一個地方引用它時残吩,計(jì)數(shù)器加1生棍;引用失效時計(jì)數(shù)器減1。在任何時刻計(jì)數(shù)器為0的對象就是不可能再被使用的猪钮。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單,效率高胆建。
缺點(diǎn):很難解決對象之間的相互循環(huán)引用烤低。A引用B,B引用A ——循環(huán)引用 (引用計(jì)數(shù)算法)由于A笆载、B彼此引用對方扑馁,導(dǎo)致引用計(jì)數(shù)都不為0涯呻,所以GC無法回收它們
1 public class MyObject {
2 public Object ref = null;
3 public static void main(String[] args) {
4 MyObject myObject1 = new MyObject();
5 MyObject myObject2 = new MyObject();
6 myObject1 = myObject2;
7 myObject2 = myObject1;
8 myObject1 = null;
9 myObject2 = null;
10 }
11 }
以myObject1對象為例:
1、代碼執(zhí)行到line7腻要,myObject1复罐、myObject2的引用計(jì)數(shù)均為2。
2雄家、此時將myObject1和myObject2分別置為null效诅,以前一個對象為例,它的引用計(jì)數(shù)將減1趟济。
3乱投、當(dāng)myObject1=0,垃圾回收器才能進(jìn)行垃圾回收顷编,由于 myObject2 = myObject1;因?yàn)閙yObject2持有myObject1的引用戚炫,而要清除掉這個引用的前提條件是myObject2引用的對象被回收。而myObject2的引用又被myObject1持有媳纬,也就進(jìn)入一種死循環(huán)的狀態(tài)双肤。
4、最終myObject1 和myObject2引用計(jì)數(shù)均為1层宫,導(dǎo)致不能進(jìn)行垃圾回收杨伙。
1.2 可達(dá)性分析算法
這個算法的基本思想就是通過一系列的稱為 “GC Roots” 的對象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索萌腿,節(jié)點(diǎn)所走過的路徑稱為引用鏈限匣,當(dāng)一個對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的毁菱。
GC Roots根節(jié)點(diǎn):類加載器米死、Thread、虛擬機(jī)棧的本地變量表贮庞、static成員峦筒、常量引用、本地方法棧的變量等等
1.3 finalize()方法最終判定對象是否存活
即使在可達(dá)性分析算法中不可達(dá)的對象窗慎,也并非是“非死不可”的物喷,這時候它們暫時處于“緩刑”階段,要真正宣告一個對象死亡遮斥,至少要經(jīng)歷再次標(biāo)記過程峦失。
標(biāo)記的前提是對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈。
- 第一次標(biāo)記并進(jìn)行一次篩選术吗。
篩選的條件是此對象是否有必要執(zhí)行finalize()方法尉辑。
當(dāng)對象沒有覆蓋finalize方法,或者finzlize方法已經(jīng)被虛擬機(jī)調(diào)用過较屿,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”隧魄,對象被回收卓练。 - 第二次標(biāo)記
如果這個對象被判定為有必要執(zhí)行finalize()方法,那么這個對象將會被放置在一個名為:F-Queue的隊(duì)列之中购啄,并在稍后由一條虛擬機(jī)自動建立的襟企、低優(yōu)先級的Finalizer線程去執(zhí)行。這里所謂的“執(zhí)行”是指虛擬機(jī)會觸發(fā)這個方法闸溃,但并不承諾會等待它運(yùn)行結(jié)束整吆。這樣做的原因是,如果一個對象finalize()方法中執(zhí)行緩慢辉川,或者發(fā)生死循環(huán)(更極端的情況)表蝙,將很可能會導(dǎo)致F-Queue隊(duì)列中的其他對象永久處于等待狀態(tài),甚至導(dǎo)致整個內(nèi)存回收系統(tǒng)崩潰乓旗。
finalize()方法是對象脫逃死亡命運(yùn)的最后一次機(jī)會府蛇,稍后GC將對F-Queue中的對象進(jìn)行第二次小規(guī)模標(biāo)記,如果對象要在finalize()中成功拯救自己----只要重新與引用鏈上的任何的一個對象建立關(guān)聯(lián)即可屿愚,譬如把自己賦值給某個類變量或?qū)ο蟮某蓡T變量汇跨,那在第二次標(biāo)記時它將移除出“即將回收”的集合。如果對象這時候還沒逃脫妆距,那基本上它就真的被回收了穷遂。
見示例程序:
@Data
public class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("關(guān)閉資源! user"+ id + "即將被回收");
}
}
public class FinalizeTest {
public static void main(String[] args) {
List<User> list = new ArrayList<User>();
int i = 0;
int j = 0;
while (true) {
list.add(new User(i++, UUID.randomUUID().toString()));
new User(j--, UUID.randomUUID().toString());
}
}
}
輸出
...
關(guān)閉資源! user-31945即將被回收
關(guān)閉資源! user-31946即將被回收
關(guān)閉資源! user-31947即將被回收
關(guān)閉資源! user-31948即將被回收
關(guān)閉資源! user-31949即將被回收
關(guān)閉資源! user-31951即將被回收
關(guān)閉資源! user-31952即將被回收
關(guān)閉資源! user-31953即將被回收
...
一旦垃圾回收器準(zhǔn)備好釋放對象占用的存儲空間,將首先調(diào)用其finalize() 方法娱据,并且在下一次垃圾回收動作發(fā)生時蚪黑,才真正回收對象占用的內(nèi)存。
注意:finalize只有垃圾回收GC的時候才會去調(diào)用中剩,但程序不一定會GC忌穿。
finalize未執(zhí)行
public class FinalizeTest {
public static void main(String[] args) {
FinalizeTest ft = new FinalizeTest();
}
@Override
protected void finalize() throws Throwable {
System.out.println("it is finalized!");
}
}
finalize只有垃圾回收的時候才會去調(diào)用。操作系統(tǒng)結(jié)束一個進(jìn)程结啼,會把那個進(jìn)程申請的內(nèi)存都清了掠剑,所以用不到gc
finalize執(zhí)行
public class FinalizeTest {
public static void main(String[] args) throws Exception {
FinalizeTest ft = new FinalizeTest();
//表示ttf已經(jīng)不可能有任何線程會使用它了
ft = null;
//運(yùn)行垃圾回收器
System.gc();
}
@Override
protected void finalize() throws Throwable {
System.out.println("it is finalized!");
}
}
jvm在什么情況下會執(zhí)行GC?
- 對象沒有引用
- 作用域發(fā)生未捕獲異常
- 程序在作用域正常執(zhí)行完畢
- 程序執(zhí)行了System.exit()
- 程序發(fā)生意外終止(被殺進(jìn)程等)
1.4 如何判斷一個常量是廢棄常量
運(yùn)行時常量池主要回收的是廢棄的常量。那么郊愧,我們?nèi)绾闻袛嘁粋€常量是廢棄常量呢朴译?
假如在常量池中存在字符串 "abc",如果當(dāng)前沒有任何String對象引用該字符串常量的話属铁,就說明常量 "abc" 就是廢棄常量眠寿,如果這時發(fā)生內(nèi)存回收的話而且有必要的話,"abc" 就會被系統(tǒng)清理出常量池红选。
1.5 如何判斷一個類是無用的類
方法區(qū)主要回收的是無用的類澜公,那么如何判斷一個類是無用的類的呢姆另?
判定一個常量是否是“廢棄常量”比較簡單喇肋,而要判定一個類是否是“無用的類”的條件則相對苛刻許多坟乾。類需要同時滿足下面3個條件才能算是 “無用的類” :
該類所有的實(shí)例都已經(jīng)被回收,也就是 Java 堆中不存在該類的任何實(shí)例蝶防。
加載該類的 ClassLoader 已經(jīng)被回收甚侣。
該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法间学。
虛擬機(jī)可以對滿足上述3個條件的無用類進(jìn)行回收殷费,這里說的僅僅是“可以”,而并不是和對象一樣不使用了就會必然被回收低葫。
2.垃圾收集算法
2.1 標(biāo)記-清除算法
算法分為“標(biāo)記”和“清除”階段:首先標(biāo)記出所有需要回收的對象详羡,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。它是最基礎(chǔ)的收集算法嘿悬,效率也很高实柠,但是會帶來兩個明顯的問題:
效率問題
空間問題(標(biāo)記清除后會產(chǎn)生大量不連續(xù)的碎片)
2.2 復(fù)制算法
為了解決效率問題,“復(fù)制”收集算法出現(xiàn)了善涨。它可以將內(nèi)存分為大小相同的兩塊窒盐,每次使用其中的一塊。當(dāng)這一塊的內(nèi)存使用完后钢拧,就將還存活的對象復(fù)制到另一塊去蟹漓,然后再把使用的空間一次清理掉。這樣就使每次的內(nèi)存回收都是對內(nèi)存區(qū)間的一半進(jìn)行回收源内。
2.3 標(biāo)記-整理算法
根據(jù)老年代的特點(diǎn)特出的一種標(biāo)記算法葡粒,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象回收姿锭,而是讓所有存活的對象向一段移動塔鳍,然后直接清理掉端邊界以外的內(nèi)存。
2.4 分代收集算法
當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法呻此,這種算法沒有什么新的思想轮纫,只是根據(jù)對象存活周期的不同將內(nèi)存分為幾塊。一般將java堆分為新生代和老年代焚鲜,這樣我們就可以根據(jù)各個年代的特點(diǎn)選擇合適的垃圾收集算法掌唾。
比如在新生代中,每次收集都會有大量對象死去忿磅,所以可以選擇復(fù)制算法糯彬,只需要付出少量對象的復(fù)制成本就可以完成每次垃圾收集。而老年代的對象存活幾率是比較高的葱她,而且沒有額外的空間對它進(jìn)行分配擔(dān)保撩扒,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集。