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


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

1、堆中“死”對象

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

1.1如何判定對象“已死”

1.1.1引用計數法

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

1.1.2可達性分析

  基本思路是找一些對象作為遍歷的起始點(成為GC Roots),從這些起始點開始搜索,當某個對象並沒有和任何GC Root產生關聯,則認為這個對象已經不被使用了,可以清除。通常,可作為GC Root的對象有以下幾種:

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

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

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

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

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

1.2關於引用

  上文談到的引用,是我們平時經常談到的、最直白的“引用”。在JDK1.2之前,“引用”的定義是:如果reference類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱這塊內存代表着一個引用。這種定義非常的純粹,非黑即白。在內存回收的時候也是,有用就留着,沒用立刻刪。但有一些場景,例如,當內存充足的時候,某塊內存留着備用;內存不充足的時候,回收這塊內存。這時純粹的“引用”就沒辦法了。在JDK1.2之后,Java對引用進行了擴展,將引用分為強引用、軟引用、弱引用、虛引用。

  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)虛引用:最弱的引用,一個對象是否存在虛引用,並不影響其生存。為一個對象設置虛引用的唯一目的是在該對象被回收時,能夠收到一個系統通知。 虛引用主要用來跟蹤對象被垃圾回收器回收的活動。

2、方法區內內存

  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)該類的類對象沒有在任何地方引用,無法反射出這個類的方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM