JVM內存回收機制——哪些內存須要被回收(JVM學習系列2)

  上一篇文章中討論了Java內存運行時的各個區域,其中程序計數器、虛擬機棧、本地方法棧隨線程生滅,且建立時須要多少內存,基本上在譯期間就決定的了,因此在內存回收時無需特殊的關注。而堆和方法區則不一樣,首先堆中只能在運行時,隨着方法的調用而肯定建立哪些對象;方法區中也一樣如此,常量池中的常量、加載的類信息也是隨時在發生着變化且不可預知。因此說,JVM內存回收,主要針對的是這兩部分的內容。算法

一、堆中「死」對象

  籠統的說,沒用的對象就是死對象。緩存

1.1如何斷定對象「已死」

1.1.1引用計數法

  給對象添加一個引用計數器,當有其餘對象引用該對象時,計數器+1;當引用失效時,計數器-1。原理簡單,實現容易,聽起來也不錯。但這個算法沒法解決對象間循環引用的問題。也就是說對象A引用了對象B,而對象B同時也引用了對象A,此時就造成了循環引用。這樣兩個對象就永遠都不會被回收。主流的JVM中均沒有采用這種算法。數據結構

1.1.2可達性分析

  基本思路是找一些對象做爲遍歷的起始點(成爲GC Roots),從這些起始點開始搜索,當某個對象並無和任何GC Root產生關聯,則認爲這個對象已經不被使用了,能夠清除。一般,可做爲GC Root的對象有如下幾種:框架

    1)虛擬機棧,棧幀中的本地變量表中引用的對象學習

    2)方法區中加載的類的靜態屬性引用的對象spa

    3)方法區中常量引用的對象(疑問,方法區中的常量到底有哪些)線程

    4)本地方法棧中引用的對象3d

  由上可見,可做爲GC Roots的對象基本上能夠歸類爲全局性引用和執行上下文,而應用中這些對象實在太多,這就致使根節點的遍歷(或者叫肯定GC Roots)勢必會消耗不少時間,那是如何肯定GC Roots的呢?當前主流Java虛擬機使用的都是準確式GC,以HotSpot虛擬機爲例,當系統肯定GC Roots時,並不須要遍歷整個方法區或者堆,而是知道哪些地方存放着對象的引用,這是有一個叫OopMap的數據結構來實現的。代理

1.2關於引用

  上文談到的引用,是咱們平時常常談到的、最直白的「引用」。在JDK1.2以前,「引用」的定義是:若是reference類型的數據中存儲的數值表明的是另一塊內存的起始地址,就稱這塊內存表明着一個引用。這種定義很是的純粹,非黑即白。在內存回收的時候也是,有用就留着,沒用馬上刪。但有一些場景,例如,當內存充足的時候,某塊內存留着備用;內存不充足的時候,回收這塊內存。這時純粹的「引用」就沒辦法了。在JDK1.2以後,Java對引用進行了擴展,將引用分爲強引用、軟引用、弱引用、虛引用。code

  1)強引用:就是上面說的純粹的引用

String str=new String("abc"); 

  2)軟引用:表示對象還有用,但不是必須的。內存不夠用的時候,纔會回收這些內存。軟引用可用來實現內存敏感的高速緩存。

String str=new String("abc");                                     // 強引用
SoftReference<String> softRef=new SoftReference<String>(str);     // 軟引用 

後續在垃圾回收時,JVM內部邏輯以下:

if(JVM.內存不足()) {
    str = null;  // 轉換爲軟引用
    System.gc(); // 垃圾回收器進行回收
}

  3)弱引用:表示對象還有用,但不是必須的,且不如軟引用「硬」。只要發生垃圾回收,這些對象就會被回收。

String str=new String("abc");    
WeakReference<String> weakRef = new WeakReference<String>(str);

後續在垃圾回收時,JVM內部邏輯以下:

str = null;

  4)虛引用:最弱的引用,一個對象是否存在虛引用,並不影響其生存。爲一個對象設置虛引用的惟一目的是在該對象被回收時,可以收到一個系統通知。 虛引用主要用來跟蹤對象被垃圾回收器回收的活動。

二、方法區內內存

  JDK1.8以前方法區是放到永久代中實現的(HotSpot虛擬機)。對於堆中的內存回收,尤爲是新生代,回收率可以達到70%~95%;而永久代中的回收率則很是低。也就是說,回收方法區內的內存性價比很低。但這塊內存又不能沒有垃圾回收機制,SUN公司就曾公佈過關於方法區內存泄漏的嚴重BUG。

  方法區中垃圾回收主要關注兩部分:無用的常量和類。

  常量的回收與堆中對象的回收機制相似,當某個常量沒有被任何對象引用的時候,這個常量就沒有用了,就能夠被回收。舉例,當前系統中找不到任何String對象引用了常量池中的的某個字符串常量:abcd,那麼abcd這個常量就會被回收。同理,常量池中其餘類、方法、字段的符號引用也與此相似。

  回收類的效率很是低,但在當前企業級應用大量使用反射(Spring IOC,在bean注入的時候,經過反射實例化一個類,將其經過setter方法放到bean中)、動態代理(Spring AOP,AOP框架不會去修改字節碼,而是在內存中臨時爲方法生成一個AOP對象,這個AOP對象包含了目標對象的所有方法,而且在特定的切點作了加強處理,並回調原對象的方法。)、CGLib(CGLib技術後續繼續學習)等技術的前提下,類的回收也變得很重要。已加載類的回收條件很是苛刻,須要知足如下三個條件,纔有可能被JVM回收:

    1)該類產生的對象實例均被回收

    2)加載該類的ClassLoader已經被回收(類加載機制後續繼續學習)

    3)該類的類對象沒有在任何地方引用,沒法反射出這個類的方法

相關文章
相關標籤/搜索