JVM_11 垃圾回收2-垃圾回收相關概念

完整JVM學習筆記請戳

System.gc()的理解

  • 在默認狀況下,經過System.gc ()或者Runtime . getRuntime() .gc()的調用,會顯式觸發Full GC,同時對老年代和新生代進行回收,嘗試釋放被丟棄對象佔用的內存。
  • 然而System.gc()調用附帶一個免責聲明,==沒法保證對垃圾收集器的調用(沒法保證立刻觸發GC)==。
  • JVM實現者能夠經過system.gc()調用來決定JVM的GC行爲。而通常狀況下,垃圾回收應該是自動進行的,無須手動觸發,不然就太過於麻煩了。在一些特殊狀況下,如咱們正在編寫一個性能基準,咱們能夠在運行之,間調用System.gc()。
  • 如下代碼,若是注掉System.runFinalization(); 那麼控制檯不保證必定打印,證實了System.gc()沒法保證GC必定執行
public class SystemGCTest {
    public static void main(String[] args) {
        new SystemGCTest();
        System.gc();//提醒jvm的垃圾回收器執行gc,可是不肯定是否立刻執行gc
        //與Runtime.getRuntime().gc();的做用同樣。
        System.runFinalization();//強制調用使用引用的對象的finalize()方法
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("SystemGCTest 重寫了finalize()");
    }
}
複製代碼

手動gc理解不可達對象的回收行爲

public class LocalVarGC {
    public void localvarGC1() {
        byte[] buffer = new byte[10 * 1024 * 1024];//10MB
        System.gc();
        //輸出: 不會被回收, FullGC時被放入老年代
        //[GC (System.gc()) [PSYoungGen: 14174K->10736K(76288K)] 14174K->10788K(251392K), 0.0089741 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
        //[Full GC (System.gc()) [PSYoungGen: 10736K->0K(76288K)] [ParOldGen: 52K->10649K(175104K)] 10788K->10649K(251392K), [Metaspace: 3253K->3253K(1056768K)], 0.0074098 secs] [Times: user=0.01 sys=0.02, real=0.01 secs]
    }

    public void localvarGC2() {
        byte[] buffer = new byte[10 * 1024 * 1024];
        buffer = null;
        System.gc();
        //輸出: 正常被回收
        //[GC (System.gc()) [PSYoungGen: 14174K->544K(76288K)] 14174K->552K(251392K), 0.0011742 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
        //[Full GC (System.gc()) [PSYoungGen: 544K->0K(76288K)] [ParOldGen: 8K->410K(175104K)] 552K->410K(251392K), [Metaspace: 3277K->3277K(1056768K)], 0.0054702 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

    }

    public void localvarGC3() {
        {
            byte[] buffer = new byte[10 * 1024 * 1024];
        }
        System.gc();
        //輸出: 不會被回收, FullGC時被放入老年代
        //[GC (System.gc()) [PSYoungGen: 14174K->10736K(76288K)] 14174K->10784K(251392K), 0.0076032 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
        //[Full GC (System.gc()) [PSYoungGen: 10736K->0K(76288K)] [ParOldGen: 48K->10649K(175104K)] 10784K->10649K(251392K), [Metaspace: 3252K->3252K(1056768K)], 0.0096328 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
    }

    public void localvarGC4() {
        {
            byte[] buffer = new byte[10 * 1024 * 1024];
        }
        int value = 10;
        System.gc();
        //輸出: 正常被回收
        //[GC (System.gc()) [PSYoungGen: 14174K->496K(76288K)] 14174K->504K(251392K), 0.0016517 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
        //[Full GC (System.gc()) [PSYoungGen: 496K->0K(76288K)] [ParOldGen: 8K->410K(175104K)] 504K->410K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0055183 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
    }

    public void localvarGC5() {
        localvarGC1();
        System.gc();
        //輸出: 正常被回收
        //[GC (System.gc()) [PSYoungGen: 14174K->10720K(76288K)] 14174K->10744K(251392K), 0.0121568 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
        //[Full GC (System.gc()) [PSYoungGen: 10720K->0K(76288K)] [ParOldGen: 24K->10650K(175104K)] 10744K->10650K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0101068 secs] [Times: user=0.01 sys=0.02, real=0.01 secs]
        //[GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] 10650K->10650K(251392K), 0.0005717 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
        //[Full GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] [ParOldGen: 10650K->410K(175104K)] 10650K->410K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0045963 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
    }

    public static void main(String[] args) {
        LocalVarGC local = new LocalVarGC();
        local.localvarGC5();
    }
}
複製代碼

內存溢出與內存泄漏

  • 內存溢出相對於內存泄漏來講,儘管更容易被理解,可是一樣的,內存溢出也是引起程序崩潰的罪魁禍首之一。
  • 因爲GC一直在發展,全部通常狀況下,除非應用程序佔用的內存增加速度很是快,形成垃圾回收已經跟不上內存消耗的速度,不然不太容易出現O0M的狀況。
  • 大多數狀況下,GC會進行各類年齡段的垃圾回收,實在不行了就放大招,來一次獨佔式的Full GC操做,這時候會回收大量的內存,供應用程序繼續使用。
  • javadoc中對OutOfMemoryError的解釋是,==沒有空閒內存,而且垃圾收集器也沒法提供更多內存==。

內存溢出

  • 首先說沒有空閒內存的狀況:說明Java虛擬機的堆內存不夠。緣由有二:
    • (1) Java虛擬機的堆內存設置不夠。
      好比:可能存在內存泄漏問題;也頗有可能就是堆的大小不合理,好比咱們要處理比較可觀的數據量,可是沒有顯式指定JVM堆大小或者指定數值偏小。咱們能夠經過參數一Xms、一Xmx來調整。
    • (2)代碼中建立了大量大對象,而且長時間不能被垃圾收集器收集(存在被引用)對於老版本的Oracle JDK,由於永久代的大小是有限的,而且JVM對永久代垃圾回收(如,常量池回收、卸載再也不須要的類型)很是不積極,因此當咱們不斷添加新類型的時候,永久代出現OutOfMemoryError也很是多見,尤爲是在運行時存在大量動態類型生成的場合;相似intern字符串緩存佔用太多空間,也會致使0OM問題。對應的異常信息,會標記出來和永久代相關: "java. lang. OutOfMemoryError: PermGen space"。
      隨着元數據區的引入,方法區內存已經再也不那麼窘迫,因此相應的00M有所改觀,出現00M,異常信息則變成了:「java. lang. OutOfMemoryError: Metaspace"。 直接內存不足,也會致使0OM。
  • 這裏面隱含着一層意思是,在拋出0utOfMemoryError之 前,一般垃圾收集器會被觸發,盡其所能去清理出空間。
    • ➢例如:在引用機制分析中,涉及到JVM會去嘗試回收軟引用指向的對象等。
    • ➢在java.nio.BIts.reserveMemory()方法中,咱們能清楚的看到,System.gc()會被調用,以清理空間。
  • 固然,也不是在任何狀況下垃圾收集器都會被觸發的
    • ➢好比,咱們去分配一一個超大對象,相似一個超大數組超過堆的最大值,JVM能夠判斷出垃圾收集並不能解決這個問題,因此直接拋出OutOfMemoryError

內存泄漏(Memory Leak)

  • 也稱做「存儲滲漏」。嚴格來講,==只有對象不會再被程序用到了,可是GC又不能回收他們的狀況,才叫內存泄漏==。
  • 但實際狀況不少時候一些不太好的實踐(或疏忽)會致使對象的生命週期變得很長甚至致使0OM,也能夠叫作寬泛意義上的「內存泄漏
  • 儘管內存泄漏並不會馬上引發程序崩潰,可是一旦發生內存泄漏,程序中的可用內存就會被逐步蠶食,直至耗盡全部內存,最終出現0utOfMemory異常,致使程序崩潰。
  • 注意,這裏的存儲空間並非指物理內存,而是指虛擬內存大小,這個虛擬內存大小取決於磁盤交換區設定的大小。
  • 1

舉例java

  • 一、單例模式
    單例的生命週期和應用程序是同樣長的,因此單例程序中,若是持有對外部對象的引用的話,那麼這個外部對象是不能被回收的,則會致使內存泄漏的產生。
  • 二、一些提供close的資源未關閉致使內存泄漏 數據庫鏈接( dataSourse. getConnection()),網絡鏈接(socket)和io鏈接必須手動close,不然是不能被回收的。

Stop The World

  • Stop一the一World,簡稱STW,指的是Gc事件發生過程當中,會產生應用程序的停頓。停頓產生時整個應用程序線程都會被暫停,沒有任何響應,有點像卡死的感受,這個停頓稱爲STW。.
    • ➢可達性分析算法中枚舉根節點(GC Roots)會致使全部Java執行線程停頓。.
      • 分析工做必須在一個能確保一致性的快照 中進行
      • 一致性指整個分析期間整個執行系統看起來像被凍結在某個時間點上V- - 若是出現分析過程當中對象引用關係還在不斷變化,則分析結果的準確性沒法保證
  • 被STW中斷的應用程序線程會在完成GC以後恢復,頻繁中斷會讓用戶感受像是網速不快形成電影卡帶同樣, 因此咱們須要減小STW的發生。
  • STW事件和採用哪款GC無關,全部的GC都有這個事件。
  • 哪怕是G1也不能徹底避免Stop一the一world狀況發生,只能說垃圾回收器愈來愈優秀,回收效率愈來愈高,儘量地縮短了暫停時間。
  • STW是JVM在後臺自動發起和自動完成的。在用戶不可見的狀況下,把用戶正常的工做線程所有停掉。
  • 開發中不要用System.gc();會致使Stop一the一world的發生。

測試代碼git

public class StopTheWorldDemo {
    public static class WorkThread extends Thread {
        List<byte[]> list = new ArrayList<byte[]>();

        public void run() {
            try {
                while (true) {
                    for(int i = 0;i < 1000;i++){
                        byte[] buffer = new byte[1024];
                        list.add(buffer);
                    }

                    if(list.size() > 10000){
                        list.clear();
                        System.gc();//會觸發full gc,進而會出現STW事件
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static class PrintThread extends Thread {
        public final long startTime = System.currentTimeMillis();

        public void run() {
            try {
                while (true) {
                    // 每秒打印時間信息
                    long t = System.currentTimeMillis() - startTime;
                    System.out.println(t / 1000 + "." + t % 1000);
                    Thread.sleep(1000);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        WorkThread w = new WorkThread();
        PrintThread p = new PrintThread();
        w.start();
        p.start();
    }
}
複製代碼

垃圾回收的並行與併發

併發(Concurrent)

  • 在操做系統中,是指一個時間段中有幾個程序都處於己啓動運行到運行完畢之間,且這幾個程序都是在同一個處理器_上運行。
  • 併發不是真正意義上的「同時進行」,只是CPU把一個時間段劃分紅幾個時間片斷(時間區間),而後在這幾個時間區間之間來回切換,因爲CPU處理的速度很是快,只要時間間隔處理得當,便可讓用戶感受是多個應用程序同時在進行。
  • 2

並行(Parallel)

  • 當系統有一個以上CPU時,當一個CPU執行一個進程時,另外一個CPU能夠執行另外一個進程,兩個進程互不搶佔CPU資源,能夠同時進行,咱們稱之爲並行(Parallel)。
  • 其實==決定並行的因素不是CPU的數量,而是CPU的核心數量==,好比一個CPU多個核也能夠 並行。
  • 適合科學計算,後臺處理等弱交互場景
  • 3

兩者對比

  • 併發,指的是多個事情,在同一時間段內同時發生了。
  • 並行,指的是多個事情,在同一時間點上同時發生了。
  • 併發的多個任務之間是互相搶佔資源的。
  • 並行的多個任務之間是不互相搶佔資源的。
  • 只有在多CPU或者一個CPU多核的狀況中,纔會發生並行。不然,看似同時發生的事情,其實都是併發執行的。

垃圾回收的併發與並行

併發和並行,在談論垃圾收集器的上下文語境中,它們能夠解釋以下:github

  • 並行(Parallel) :指多條垃圾收集線程並行工做,但此時用戶線程仍處於等待狀態。面試

    • 如ParNew、 Parallel Scavenge、 Parallel 0ld;
  • 串行(Serial)算法

    • 相較於並行的概念,單線程執行。
    • 若是內存不夠,則程序暫停,啓動JVM垃圾回收器進行垃圾回收。回收完,再啓動程序的線程。
  • 4
  • 併發(Concurrent) :指用戶線程與垃圾收集線程同時執行(但不必定是並行的,可能會交替執行),垃圾回收線程在執行時不會停頓用戶程序的運行。數據庫

    • ➢用戶程序在繼續運行,而垃圾收集程序線程運行於另外一個CPU上;
    • ➢如: CMS、G1
  • 5

安全點與安全區域

安全點(Safepoint)

  • 程序執行時並不是在全部地方都能停頓下來開始GC,只有在特定的位置才能停頓下來開始GC,這些位置稱爲「安全點(Safepoint) 」
  • Safe Point的選擇很重要,若是太少可能致使GC等待的時間太長,若是太頻繁可能致使運行時的性能問題。大部分指令的執行時間都很是短暫,一般會根據「是否具備讓程序長時間執行的特徵」爲標準。好比:選擇些執行時間較長的指令做爲Safe Point, 如方法調用、循環跳轉和異常跳轉等。

如何在GC發生時,檢查全部線程都跑到最近的安全點停頓下來呢?數組

  • 搶先式中斷: (目前沒有虛擬機採用了) 首先中斷全部線程。若是還有線程不在安全點,就恢復線程,讓線程跑到安全點。
  • 主動式中斷: 設置一箇中斷標誌,各個線程運行到Safe Point的時候主動輪詢這個標誌,若是中斷標誌爲真,則將本身進行中斷掛起。

安全區域(Safe Region)

  Safepoint機制保證了程序執行時,在不太長的時間內就會遇到可進入GC的Safepoint 。可是,程序「不執行」的時候呢?例如線程處於Sleep 狀態或Blocked狀態,這時候線程沒法響應JVM的中斷請求,「走」 到安全點去中斷掛起,JVM也不太可能等待線程被喚醒。對於這種狀況,就須要安全區域(Safe Region)來解決。
  安全區域是指在一段代碼片斷中,對象的引用關係不會發生變化,在這個區域中的任何位置開始GC都是安全的。咱們也能夠把Safe Region 看作是被擴展了的Safepoint。緩存

實際執行時:安全

  • 一、當線程運行到Safe Region的代碼時,首先標識已經進入了Safe Region,若是這段時間內發生GC,JVM會 忽略標識爲Safe Region狀態 的線程;
  • 二、當線程即將離開Safe Region時, 會檢查JVM是否已經完成GC,若是完成了,則繼續運行,不然線程必須等待直到收到能夠安全離開SafeRegion的信號爲止;

引用

  • 咱們但願能描述這樣一類對象: 當內存空間還足夠時,則能保留在內存中;若是內存空間在進行垃圾收集後仍是很緊張,則能夠拋棄這些對象。 -【既偏門又很是高頻的面試題】強引用、軟引用、弱引用、虛引用有什麼區別?具體使用.場景是什麼?
  • 在JDK 1.2版以後,Java對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference) 、弱引用(Weak Reference) 和虛引用(Phantom Reference) 4種,這4種引用強度依次逐漸減弱。
  • 除強引用外,其餘3種引用都可以在java.lang.ref包中找到它們的身影。以下圖,顯示了這3種引用類型對應的類,開發人員能夠在應用程序中直接使用它們。
  • 6

Reference子類中只有終結器引用是包內可見的,其餘3種引用類型均爲public,能夠在應用程序中直接使用bash

  • 強引用(StrongReference)I :最傳統的「引用」的定義,是指在程序代碼之中廣泛存在的引用賦值,即相似「0bject obj=new object( )」這種引用關係。==不管任何狀況下,只要強引用關係還存在,垃圾收集器就永遠不會回收掉被引用的對象==。
  • 軟引用(SoftReference) :在系統將要發生內存溢出以前,將會把這些對象列入回收範圍之中進行第二次回收。若是此次回收後尚未足夠的內存,纔會拋出內存溢出異常。
  • 弱引用(WeakReference) :被弱引用關聯的對象只能生存到下一次垃圾收集以前。當垃圾收集器工做時,不管內存空間是否足夠,都會回收掉被弱引用關聯的對象。
  • 虛引用(PhantomReference) :一個對象是否有虛引用的存在,徹底不會對其生存時 間構成影響,也沒法經過虛引用來得到一個對象的實例。==爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知(回收跟蹤)==。

強引用: 不回收

  • 在Java程序中,最多見的引用類型是強引用(普通系統99%以上都是強引用),也就是咱們最多見的普通對象引用,也是默認的引用類型。
  • 當在Java語言中使用new操做符建立一個新的對象, 並將其賦值給一個變量的時候,這個變量就成爲指向該對象的一個強引用。
  • 強引用的對象是可觸及的,垃圾收集器就永遠不會回收掉被引用的對象。
  • 對於一一個普通的對象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯式地將相應(強)引用賦值爲null,就是能夠當作垃圾被收集了,固然具體回收時機仍是要看垃圾收集策略。
  • 相對的,軟引用、 弱引用和虛引用的對象是軟可觸及、弱可觸及和虛可觸及的,在必定條件下,都是能夠被回收的。因此,強引用是形成Java內存泄漏的主要緣由之一。
測試代碼
public class StrongReferenceTest {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer ("Hello,尚硅谷");
        StringBuffer str1 = str;

        str = null;
        System.gc();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(str1);
    }
}
複製代碼

StringBuffer str = new StringBuffer ("Hello,尚硅谷");
局部變量str指向StringBuffer實例所在堆空間,經過str能夠操做該實例,那麼str就是StringBuffer實例的強引用
對應內存結構:
7
此時,若是再運行一個賦值語句:
StringBuffer str1 = str;
對應內存結構:
8
本例中的兩個引用,都是強引用,強引用具有如下特色:

  • 強引用能夠直接訪問目標對象。
  • 強引用所指向的對象在任什麼時候候都不會被系統回收,虛擬機寧願拋出OOM異常,也不會回收強引用所指向對象。
  • 強引用可能致使內存泄漏。

軟引用: 內存不足即回收

  • 軟引用是用來描述一 些還有用,但非必需的對象。只被軟引用關聯着的對象,在系統將要發生內存溢出異常前,會把這些對象列進回收範圍之中進行第二次回收,若是此次回收尚未足夠的內存,纔會拋出內存溢出異常。
  • ==軟引用一般用來實現內存敏感的緩存==。好比:高速緩存就有用到軟引用。若是還有空閒內存,就能夠暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。
  • 垃圾回收器在某個時刻決定回收軟可達的對象的時候,會清理軟引用,並可選地把引用存放到一個引用隊列( Reference Queue)。
  • 相似弱引用,只不過Java虛擬機會盡可能讓軟引用的存活時間長一些,迫不得.已才清理。
  • 軟引用:
    • 當內存足夠: 不會回收軟引|用的可達對象
    • 當內存不夠時: 會回收軟引用的可達對象
  • 在JDK 1. 2版以後提供了java.lang.ref.SoftReference類來實現軟引用。
Object obj = new object(); //聲明強引用
SoftReference<0bject> sf = new SoftReference<0bject>(obj);
obj = null; //銷燬強引用
複製代碼
測試代碼
/**
 * 軟引用的測試:內存不足即回收
 * -Xms10m -Xmx10m -XX:+PrintGCDetails
 */
public class SoftReferenceTest {
    public static class User {
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int id;
        public String name;

        @Override
        public String toString() {
            return "[id=" + id + ", name=" + name + "] ";
        }
    }

    public static void main(String[] args) {
        //建立對象,創建軟引用
//        SoftReference<User> userSoftRef = new SoftReference<User>(new User(1, "songhk"));
        //上面的一行代碼,等價於以下的三行代碼
        User u1 = new User(1,"songhk");
        SoftReference<User> userSoftRef = new SoftReference<User>(u1);
        u1 = null;//取消強引用


        //從軟引用中從新得到強引用對象
        System.out.println(userSoftRef.get());

        System.gc();
        System.out.println("After GC:");
//        //垃圾回收以後得到軟引用中的對象
        System.out.println(userSoftRef.get());//因爲堆空間內存足夠,全部不會回收軟引用的可達對象。
//
        try {
            //讓系統認爲內存資源緊張、不夠
//            byte[] b = new byte[1024 * 1024 * 7];
            byte[] b = new byte[1024 * 7168 - 399 * 1024];//剛好能放下數組又放不下u1的內存分配大小 不會報OOM
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            //再次從軟引用中獲取數據
            System.out.println(userSoftRef.get());//在報OOM以前,垃圾回收器會回收軟引用的可達對象。
        }
    }
}
複製代碼

弱引用: 發現即回收

  • 弱引用也是用來描述那些非必需對象,被弱引用關聯的對象只能生存到下一次垃圾收集發生爲止。在系統GC時,只要發現弱引用,無論系統堆空間使用是否充足,都會回收掉只被弱引用關聯的對象。
  • 可是,因爲垃圾回收器的線程一般優先級很低,所以,並不一 定能很快地發現持有弱引用的對象。在這種狀況下,弱引用對象能夠存在較長的時間。
  • 弱引用和軟引用同樣,在構造弱引用時,也能夠指定一個引用隊列,當弱引用對象被回收時,就會加入指定的引用隊列,經過這個隊列能夠跟蹤對象的回收狀況。
  • 軟引用、弱引用都很是適合來保存那些無關緊要的緩存數據。若是這麼作,當系統內存不足時,這些緩存數據會被回收,不會致使內存溢出。而當內存資源充足時,這些緩存數據又能夠存在至關長的時間,從而起到加速系統的做用。
  • 在JDK1.2版以後提後了java.lang.ref.WeakReference類來實現弱引用
Object obj = new object(); //聲明強引用
WeakReference<0bject> sf = new WeakReference<0bject>(obj);
obj = null; //銷燬強引用
複製代碼
  • 弱引用對象與軟引用對象的最大不一樣就在於,當GC在進行回收時,須要經過算法檢查是否回收軟引用對象,而對於弱引用對象,GC老是進行回收。弱引用對象更容易、更快被GC回收。
  • 面試題:你開發中使用過WeakHashMap嗎?
    • 經過查看WeakHashMap源碼,能夠看到其內部類Entry使用的就是弱引用
    • line 702 -> private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {...}
測試代碼
public class WeakReferenceTest {
    public static class User {
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int id;
        public String name;

        @Override
        public String toString() {
            return "[id=" + id + ", name=" + name + "] ";
        }
    }

    public static void main(String[] args) {
        //構造了弱引用
        WeakReference<User> userWeakRef = new WeakReference<User>(new User(1, "songhk"));
        //從弱引用中從新獲取對象
        System.out.println(userWeakRef.get());

        System.gc();
        // 無論當前內存空間足夠與否,都會回收它的內存
        System.out.println("After GC:");
        //從新嘗試從弱引用中獲取對象
        System.out.println(userWeakRef.get());
    }
}
複製代碼

虛引用: 對象回收跟蹤

  • 虛引用(Phantom Reference),也稱爲「幽靈引用」或者「幻影引用」,是全部引用類型中最弱的一個。
  • 一個對象是否有虛引用的存在,徹底不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它和沒有引用幾乎是同樣的,隨時均可能被垃圾回收器回收。
  • 它不能單獨使用,也沒法經過虛引用來獲取被引用的對象。當試圖經過虛引用的get()方法取得對象時,老是null。
  • ==爲一個對象設置虛引用關聯的惟一目的在於跟蹤垃圾回收過程==。好比:能在這個對象被收集器回收時收到一個系統通知。
  • 虛引用必須和引用隊列一塊兒使用。虛引用在建立時必須提供一個引用隊列做爲參數。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象後,將這個虛引用加入引用隊列,以通知應用程序對象的回收狀況。
  • 因爲虛引用能夠跟蹤對象的回收時間,所以,也能夠將一些資源釋放操做放置在虛引用中執行和記錄。
  • 在JDK 1. 2版以後提供了PhantomReference類來實現虛引用。
object obj = new object();
ReferenceQueuephantomQueue = new ReferenceQueue( ) ;
PhantomReference<object> pf = new PhantomReference<object>(obj, phantomQueue); 
obj = null;
複製代碼
測試代碼
public class PhantomReferenceTest {
    public static PhantomReferenceTest obj;//當前類對象的聲明
    static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;//引用隊列

    public static class CheckRefQueue extends Thread {
        @Override
        public void run() {
            while (true) {
                if (phantomQueue != null) {
                    PhantomReference<PhantomReferenceTest> objt = null;
                    try {
                        objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (objt != null) {
                        System.out.println("追蹤垃圾回收過程:PhantomReferenceTest實例被GC了");
                    }
                }
            }
        }
    }

    @Override
    protected void finalize() throws Throwable { //finalize()方法只能被調用一次!
        super.finalize();
        System.out.println("調用當前類的finalize()方法");
        obj = this;
    }

    public static void main(String[] args) {
        Thread t = new CheckRefQueue();
        t.setDaemon(true);//設置爲守護線程:當程序中沒有非守護線程時,守護線程也就執行結束。
        t.start();

        phantomQueue = new ReferenceQueue<PhantomReferenceTest>();
        obj = new PhantomReferenceTest();
        //構造了 PhantomReferenceTest 對象的虛引用,並指定了引用隊列
        PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);

        try {
            //不可獲取虛引用中的對象
            System.out.println(phantomRef.get());

            //將強引用去除
            obj = null;
            //第一次進行GC,因爲對象可復活,GC沒法回收該對象
            System.gc();
            Thread.sleep(1000);
            if (obj == null) {
                System.out.println("obj 是 null");
            } else {
                System.out.println("obj 可用");
            }
            System.out.println("第 2 次 gc");
            obj = null;
            System.gc(); //一旦將obj對象回收,就會將此虛引用存放到引用隊列中。
            Thread.sleep(1000);
            if (obj == null) {
                System.out.println("obj 是 null");
            } else {
                System.out.println("obj 可用");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

輸出

null
調用當前類的finalize()方法
obj 可用
第 2 次 gc
追蹤垃圾回收過程:PhantomReferenceTest實例被GC了
obj 是 null
複製代碼

終結器引用

  • 它用以實現對象的finalize()方法,也能夠稱爲終結器引用。
  • 無需手動編碼, 其內部配合引用隊列使用。
  • 在GC時, 終結器引用入隊。由Finali zer線程經過終結器引用找到被引用對象並調用它的finalize()方法,第二次GC時才能回收被引用對象。


JVM學習代碼及筆記(陸續更新中...)

【代碼】
github.com/willShuhuan…
【筆記】
JVM_01 簡介
JVM_02 類加載子系統
JVM_03 運行時數據區1- [程序計數器+虛擬機棧+本地方法棧]
JVM_04 本地方法接口
JVM_05 運行時數據區2-堆
JVM_06 運行時數據區3-方法區
JVM_07 運行時數據區4-對象的實例化內存佈局與訪問定位+直接內存
JVM_08 執行引擎(Execution Engine)
JVM_09 字符串常量池StringTable
JVM_10 垃圾回收1-概述+相關算法
JVM_11 垃圾回收2-垃圾回收相關概念
JVM_12 垃圾回收3-垃圾回收器

相關文章
相關標籤/搜索