JVM內(nèi)存管理—內(nèi)存回收—哪些內(nèi)存需要回收?

哪些區(qū)域需要回收

運(yùn)行時(shí)內(nèi)存分為5個(gè)區(qū)域:程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧、堆、方法區(qū),這些區(qū)域是如何回收的?

1-程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧

這3個(gè)區(qū)域是隨著線程而生,隨著線程而滅,棧中的棧幀數(shù)據(jù)隨著方法的調(diào)用到結(jié)束有條不紊的進(jìn)行著入棧出棧,每一個(gè)棧幀中分配多少內(nèi)部基本上在類(lèi)結(jié)構(gòu)確定下來(lái)(類(lèi)加載)就已知了。內(nèi)存分配和回收具有確定性,隨著方法的結(jié)束或者線程的結(jié)束后,內(nèi)存自然就會(huì)被回收了。

2- java堆和方法區(qū)

一個(gè)接口中的多個(gè)實(shí)現(xiàn)類(lèi)需要的內(nèi)存可能不一樣,一個(gè)方法的多個(gè)分支需要的內(nèi)存也不一樣,我們只有在程序處于執(zhí)行期間才能知道會(huì)創(chuàng)建哪些對(duì)象,對(duì)這部分的內(nèi)存分配和回收是動(dòng)態(tài)的,垃圾回收主要關(guān)注的是這部分的內(nèi)存。

判斷對(duì)象是“生”是“死”?

引用計(jì)數(shù)算法

java虛擬機(jī)沒(méi)有采用引用計(jì)數(shù)算法來(lái)判斷對(duì)象是否生存,主要考慮的是它很難解決對(duì)象之間的相互循環(huán)引用的問(wèn)題。

可達(dá)性分析算法(Reachablity Analysis)

基本思路是:從每一個(gè)GC Roots出發(fā)找到它所有可達(dá)的對(duì)象,走過(guò)的路徑成為引用鏈,被引用鏈串起來(lái)的就是存活對(duì)象;其他的雖然存在引用關(guān)系,但是GC Roots不可達(dá)的,判定為可回收對(duì)象。、
GC Roots :可作為的對(duì)象包含以下幾種:

  1. 虛擬機(jī)棧(棧幀中本地變量表)中引用的對(duì)象
  2. 方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象
  3. 方法區(qū)常量引用的對(duì)象
  4. 本地方法棧中JNI(Native Method)引用的對(duì)象

引用與緩存

為了更細(xì)致的管理對(duì)象的內(nèi)存,對(duì)象引用進(jìn)行了擴(kuò)展,分為:強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference,也叫幽靈引用)。

強(qiáng)引用
  • Object obj = new Object(); 這種通過(guò)關(guān)鍵字new出來(lái)的對(duì)象就屬于強(qiáng)引用,普遍存在的,只要這種引用還關(guān)聯(lián)著對(duì)象,就不會(huì)被垃圾回收器回收。
  • **內(nèi)存空間不夠時(shí),不會(huì)進(jìn)行垃圾回收,而是直接拋出內(nèi)存溢出異常 **
軟引用

用來(lái)描述有用但是并不必需的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,JVM將會(huì)把這些對(duì)象標(biāo)記成垃圾數(shù)據(jù),并對(duì)這些標(biāo)記的數(shù)據(jù)進(jìn)行二次回收,若回收后還是沒(méi)有足夠的內(nèi)存分配,才會(huì)拋出內(nèi)存溢出的異常。 用法: 用來(lái)實(shí)現(xiàn)內(nèi)存敏感的緩存

import java.lang.ref.SoftReference;
class ReferenceTest{
    Object obj = new Object();//強(qiáng)引用
    SoftReference<Object> sr = new SoftReference<Object>(obj);//關(guān)聯(lián)軟引用
    // 取消強(qiáng)引用,只保留軟引用
    obj=null;
    pass(...);
    //通用的處理,以及softreference的復(fù)用模式
    if(sr.get() != null){
        Obeject getRef = sr.get();
        use(getRef);
    }else{
        Object newObj = new Object();
        sr = new SoftReference(newObj);//重建軟引用
    }
}
  • 注意
    1.在虛擬機(jī)內(nèi)存空間足夠大的時(shí)候是不會(huì)發(fā)生內(nèi)存回收的,而且GC的優(yōu)先級(jí)最低,只有所有線程都停止時(shí)才有可能執(zhí)行GC(即使使用System.gc()也只是告訴JVM這是一個(gè)執(zhí)行GC的好時(shí)機(jī),但是實(shí)際上JVM會(huì)自己決斷是否達(dá)到了這個(gè)條件,比如沒(méi)有線程正在執(zhí)行,這是GC這個(gè)守護(hù)線程就會(huì)執(zhí)行),因?yàn)镚C有一定的代價(jià)。所以與軟引用關(guān)聯(lián)的對(duì)象在內(nèi)存充足時(shí)是不會(huì)發(fā)生的,只有處于內(nèi)存溢出的邊緣才會(huì)發(fā)生回收操作。
    2.雖然與軟引用關(guān)聯(lián)的對(duì)象可能會(huì)被收回,但保存這個(gè)軟引用關(guān)系的SoftReference變量并不一定會(huì)被回收,所以出現(xiàn)了ReferenceQueue來(lái)管理這些變量。
弱引用
  • 弱引用描述的是非必需的對(duì)象:被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次GC回收發(fā)生之前,當(dāng)GC工作時(shí),無(wú)論當(dāng)前空間是否足夠,都會(huì)回收只被弱引用關(guān)聯(lián)的對(duì)象。
  • 用法:一般會(huì)和ReferenceQueue進(jìn)行關(guān)聯(lián)
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class WeakReferenceTest{
    public static void main(Stting[] args) throws Exception{
        Object obj = new Object();
        //ReferenceQueue的作用:
        //當(dāng)弱引用關(guān)聯(lián)的對(duì)象被標(biāo)記為垃圾,準(zhǔn)備回收時(shí),
        //會(huì)自動(dòng)把保存弱引用的對(duì)象WeakReference放到ReferenceQueue中,等待被處理
        //主要目的是:引用關(guān)聯(lián)的對(duì)象被回收了,但存儲(chǔ)引用的對(duì)象沒(méi)有及時(shí)回收會(huì)造成
        //內(nèi)存膨脹
        ReferenceQueue<Object> rf = new ReferenceQueue<Object>();
        WeakReference<Object> wf = new WeakReference<Object>(obj,rf);
        WeakReference<Object> wf_q = null;
                // poll方法,刪除隊(duì)尾引用并返回
        while(wf_q = rf.poll()!= null){
            // do something for remove reference
        }
    }
}
虛引用
  • 最弱的一種引用關(guān)系,無(wú)法通過(guò)虛引用來(lái)獲取對(duì)象的實(shí)例
  • 用途:僅僅是在這個(gè)對(duì)象被回收時(shí)收到一個(gè)系統(tǒng)的通知。
總結(jié)

對(duì)于軟引用和弱引用,被GC標(biāo)記為垃圾準(zhǔn)備回收時(shí)會(huì)清除對(duì)應(yīng)的引用,即get方法返回null,然后放入綁定的引用隊(duì)列中,理論上此時(shí)沒(méi)有真正被回收,只是沒(méi)有辦法在訪問(wèn)到;但是對(duì)于虛引用則是在發(fā)生回收時(shí)放入隊(duì)列,也是唯一一種確認(rèn)對(duì)象被回收而加入隊(duì)列的引用,可以利用這一特性做一些有趣的事。


finalize()越獄

通過(guò)一條到墻外的地下通道(finalize)、一列快速列車(chē)(F-Queue)的驚險(xiǎn)越獄大片

  • 可達(dá)性分析發(fā)現(xiàn)這個(gè)對(duì)象沒(méi)有到GC Roots的引用鏈(快到砍頭的時(shí)候了),進(jìn)行第一次標(biāo)記如果對(duì)象沒(méi)有覆蓋Object的finalize方法,或者已經(jīng)執(zhí)行過(guò)了一次finalize方法,則這個(gè)對(duì)象的finalize不會(huì)執(zhí)行,等待下一次GC被回收(蠢蛋和倒霉蛋的組合,一個(gè)不知道這個(gè)秘密通道,一個(gè)越獄失敗被抓了回來(lái)成為重點(diǎn)照顧對(duì)象,沒(méi)日沒(méi)夜的毒打,只能等死了)
  • 如果覆蓋了finalize方法但還沒(méi)有執(zhí)行(已經(jīng)踩好點(diǎn)了,還在做最后的精密計(jì)劃,天一黑,就行動(dòng)),將這個(gè)對(duì)象放到F-Queue中等待執(zhí)行finalize方法(通過(guò)地下秘密通道到達(dá)獄外,此時(shí)列車(chē)也到了,正在排隊(duì)刷卡上車(chē)...)
  • 以低優(yōu)先級(jí)執(zhí)行F-Queue中對(duì)象的finalize方法,進(jìn)行第二次標(biāo)記,如果finalize中將自己對(duì)象的引用與外面的引用鏈上的對(duì)象建立了鏈接,則將其移出F-Queue,成功逃脫回收(滴,學(xué)生卡,上車(chē)落座,系好安全帶,老司機(jī)要飆車(chē)了),如果沒(méi)有建立與引用鏈的鏈接,則等待下一次GC被回收(滴,余額不足,請(qǐng)及時(shí)充值,司機(jī)說(shuō)窮鬼,沒(méi)錢(qián)還想做快速列車(chē)。哎哎哎~,老司機(jī)帶帶我呀,帶帶我呀,帶我呀,我呀,呀...曾經(jīng)的秋名山車(chē)神遺棄在角落。)

進(jìn)行了兩次標(biāo)記:第一次標(biāo)記是篩選有哪些finalize方法是需要執(zhí)行的;第二次標(biāo)記是哪些finalize方法關(guān)聯(lián)了引用鏈,將其移出隊(duì)列。

public class SaveSelfTest {
    public static Scofield BreakAway= null;//逃生專(zhuān)列
    public static void main(String[] args) throws Exception{
        Scofield michael = new Scofield();
        michael.sayHi();
        
        //第一次逃脫,成功
        michael = null;//置為null,觸發(fā)回收的條件
        System.gc();
        Thread.sleep(200);
        
        if(BreakAway != null){
            BreakAway.sayHi();
        }else{
            util.print("ScoField:Please save me,I'll dead next morning!");
        }
        
        //第二次逃脫,失敗,finalize函數(shù)只能最多被執(zhí)行一次
        BreakAway = null;
        System.gc();
        Thread.sleep(200);
        
        if(BreakAway != null){
            BreakAway.sayHi();
        }else{
            util.print("ScoField:Please save me,I'll dead next morning!");
        }
        
        System.exit(0);
    }

}
class Scofield{
    public void sayHi(){
        util.print("Hi, I'm Michael ScoField!");
    }
    @Override
    protected void  finalize() throws Throwable{
        super.finalize();//Object
        util.print("Scofield gets on car!");
        SaveSelfTest.BreakAway = this;//關(guān)聯(lián)引用鏈
    }
}
class util{
    public static void print(String str){
        System.out.println(str);
    }
}
/* Output
Hi, I'm Michael ScoField!
Scofield gets on car!
Hi, I'm Michael ScoField!
ScoField:Please save me,I'll dead next morning!
*/

參考鏈接
http://www.cnblogs.com/jiangyi-uestc/p/5679331.html
http://www.importnew.com/14115.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容