上一篇:05-JVM內(nèi)存分配機(jī)制深度剖析
堆中幾乎放著所有的對(duì)象實(shí)例,對(duì)堆垃圾回收前的第一步就是要判斷哪些對(duì)象已經(jīng)死亡(即不能再被任何途徑使用的對(duì)象)。
1.引用計(jì)數(shù)法
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就加1;當(dāng)引用失效,計(jì)數(shù)器就減1;任何時(shí)候計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。
這個(gè)方法實(shí)現(xiàn)簡(jiǎn)單,效率高,但是目前主流的虛擬機(jī)中并沒(méi)有選擇這個(gè)算法來(lái)管理內(nèi)存,其最主要的原因是它很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題。 所謂對(duì)象之間的相互引用問(wèn)題,如下面代碼所示:除了對(duì)象objA 和 objB 相互引用著對(duì)方之外,這兩個(gè)對(duì)象之間再無(wú)任何引用。但是他們因?yàn)榛ハ嘁脤?duì)方,導(dǎo)致它們的引用計(jì)數(shù)器都不為0,于是引用計(jì)數(shù)算法無(wú)法通知 GC 回收器回收他們。
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
}
2.可達(dá)性分析算法
將“GC Roots” 對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索引用的對(duì)象,找到的對(duì)象都標(biāo)記為非垃圾對(duì)象,其余未標(biāo)記的對(duì)象都是垃圾對(duì)象
GC Roots根節(jié)點(diǎn):線(xiàn)程棧的本地變量、靜態(tài)變量、本地方法棧的變量等等
3.常見(jiàn)引用類(lèi)型
java的引用類(lèi)型一般分為四種:強(qiáng)引用、軟引用、弱引用、虛引用
**強(qiáng)引用:**普通的變量引用
public static User user = new User();
**軟引用:**將對(duì)象用SoftReference軟引用類(lèi)型的對(duì)象包裹,正常情況不會(huì)被回收,但是GC做完后發(fā)現(xiàn)釋放不出空間存放新的對(duì)象,則會(huì)把這些軟引用的對(duì)象回收掉。軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存。
public static SoftReference<User> user = new SoftReference<User>(new User());
軟引用在實(shí)際中有重要的應(yīng)用,例如瀏覽器的后退按鈕。按后退時(shí),這個(gè)后退時(shí)顯示的網(wǎng)頁(yè)內(nèi)容是重新進(jìn)行請(qǐng)求還是從緩存中取出呢?這就要看具體的實(shí)現(xiàn)策略了。
- 如果一個(gè)網(wǎng)頁(yè)在瀏覽結(jié)束時(shí)就進(jìn)行內(nèi)容的回收,則按后退查看前面瀏覽過(guò)的頁(yè)面時(shí),需要重新構(gòu)建
- 如果將瀏覽過(guò)的網(wǎng)頁(yè)存儲(chǔ)到內(nèi)存中會(huì)造成內(nèi)存的大量浪費(fèi),甚至?xí)斐蓛?nèi)存溢出
弱引用: 將對(duì)象用WeakReference軟引用類(lèi)型的對(duì)象包裹,弱引用跟沒(méi)引用差不多,GC會(huì)直接回收掉,很少用
public static WeakReference<User> user = new WeakReference<User>(new User());
虛引用: 虛引用也稱(chēng)為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系,幾乎不用
4.finalize()方法最終判定對(duì)象是否存活
即使在可達(dá)性分析算法中不可達(dá)的對(duì)象,也并非是“非死不可”的,這時(shí)候它們暫時(shí)處于“緩刑”階段,要真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷再次標(biāo)記過(guò)程。
標(biāo)記的前提是對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與GC Roots相連接的引用鏈。
- 第一次標(biāo)記并進(jìn)行一次篩選。
篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法。
當(dāng)對(duì)象沒(méi)有覆蓋finalize方法,對(duì)象將直接被回收。 - 第二次標(biāo)記
如果這個(gè)對(duì)象覆蓋了finalize方法,finalize方法是對(duì)象脫逃死亡命運(yùn)的最后一次機(jī)會(huì),如果對(duì)象要在finalize()中成功拯救自己,只要重新與引用鏈上的任何的一個(gè)對(duì)象建立關(guān)聯(lián)即可,譬如把自己賦值給某個(gè)類(lèi)變量或?qū)ο蟮某蓡T變量,那在第二次標(biāo)記時(shí)它將移除出“即將回收”的集合。如果對(duì)象這時(shí)候還沒(méi)逃脫,那基本上它就真的被回收了。
注意:一個(gè)對(duì)象的finalize()方法只會(huì)被執(zhí)行一次,也就是說(shuō)通過(guò)調(diào)用finalize方法自我救命的機(jī)會(huì)就一次。
示例代碼:
public class OOMTest {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
int i = 0;
int j = 0;
while (true) {
list.add(new User(i++, UUID.randomUUID().toString()));
new User(j--, UUID.randomUUID().toString());
}
}
}
//User類(lèi)需要重寫(xiě)finalize方法
@Override
protected void finalize() throws Throwable {
OOMTest.list.add(this);
System.out.println("關(guān)閉資源,userid=" + id + "即將被回收");
}
finalize()方法的運(yùn)行代價(jià)高昂, 不確定性大, 無(wú)法保證各個(gè)對(duì)象的調(diào)用順序, 如今已被官方明確聲明為不推薦使用的語(yǔ)法。 有些資料描述它適合做“關(guān)閉外部資源”之類(lèi)的清理性工作, 這完全是對(duì)finalize()方法用途的一種自我安慰。 finalize()能做的所有工作, 使用try-finally或者其他方式都可以做得更好、更及時(shí), 所以建議大家完全可以忘掉Java語(yǔ)言里面的這個(gè)方法。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-704559.html
5.如何判斷一個(gè)類(lèi)是無(wú)用的類(lèi)
方法區(qū)主要回收的是無(wú)用的類(lèi),那么如何判斷一個(gè)類(lèi)是無(wú)用的類(lèi)呢?
類(lèi)需要同時(shí)滿(mǎn)足下面3個(gè)條件才能算是 “無(wú)用的類(lèi)” :文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-704559.html
- 該類(lèi)所有的對(duì)象實(shí)例都已經(jīng)被回收,也就是 Java 堆中不存在該類(lèi)的任何實(shí)例。
- 加載該類(lèi)的 ClassLoader 已經(jīng)被回收。
- 該類(lèi)對(duì)應(yīng)的 java.lang.Class 對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪(fǎng)問(wèn)該類(lèi)的方法。
到了這里,關(guān)于06-JVM對(duì)象內(nèi)存回收機(jī)制深度剖析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!