JVM學習(三)——JVM垃圾回收——堆內存

一、JVM垃圾回收簡介

在這裏插入圖片描述
Java 的自動內存管理主要是針對對象內存的回收對象內存的分配。同時,Java 自動內存管理最核心的功能是 堆 內存中對象的分配與回收

Java 堆是垃圾收集器管理的主要區域,因此也被稱作GC 堆(Garbage Collected Heap).從垃圾回收的角度,由於現在收集器基本都採用分代垃圾收集算法,所以 Java 堆還可以細分爲:新生代和老年代:再細緻一點有:Eden 空間、From Survivor、To Survivor 空間等。進一步劃分的目的是更好地回收內存,或者更快地分配內存

堆空間的基本結構:
在這裏插入圖片描述
上圖所示的 eden 區、s0(“From”) 區、s1(“To”) 區都屬於新生代,tentired 區屬於老年代。大部分情況,對象都會首先在 Eden 區域分配,在一次新生代垃圾回收後,如果對象還存活,則會進入 s1(“To”),並且對象的年齡還會加 1(Eden 區->Survivor 區後對象的初始年齡變爲 1),當它的年齡增加到一定程度(默認爲 15 歲),就會被晉升到老年代中。
在這裏插入圖片描述

  1. 對象優先在 eden 區分配
    目前主流的垃圾收集器都會採用分代回收算法,因此需要將堆內存分爲新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。大多數情況下,對象在新生代中 eden 區分配。當 eden 區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC。

新生代 GC(Minor GC):指發生新生代的的垃圾收集動作,Minor GC 非常頻繁,回收速度一般也比較快。
老年代 GC(Major GC/Full GC):指發生在老年代的 GC,出現了 Major GC 經常會伴隨至少一次的 Minor GC(並非絕對),Major GC 的速度一般會比 Minor GC 的慢 10 倍以上。

  1. 大對象直接進入老年代
    大對象就是需要大量連續內存空間的對象(比如:字符串、數組)。這樣做是爲了,爲了避免爲大對象分配內存時由於分配擔保機制帶來的複製而降低效率。
  2. 長期存活的對象將進入老年代
    如果對象在 Eden 出生並經過第一次 Minor GC 後仍然能夠存活,並且能被 Survivor 容納的話,將被移動到 Survivor 空間中,並將對象年齡設爲 1.對象在 Survivor 中每熬過一次 MinorGC,年齡就增加 1 歲,當它的年齡增加到一定程度(默認爲 15 歲),就會被晉升到老年代中。
  3. 動態對象年齡判定
    爲了更好的適應不同程序的內存情況,虛擬機不是永遠要求對象年齡必須達到了某個值才能進入老年代,如果 Survivor 空間中相同年齡所有對象大小的總和大於 Survivor 空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無需達到要求的年齡。

二、怎麼判斷對象是否存活?

堆中幾乎放着所有的對象實例,對堆垃圾回收前的第一步就是要判斷那些對象已經死亡(即不能再被任何途徑使用的對象)。

1、引用計數法

給對象中添加一個引用計數器,每當有一個地方引用它,計數器就加 1;當引用失效,計數器就減 1;任何時候計數器爲 0 的對象就是不可能再被使用的。

這個方法實現簡單,效率高,但是目前主流的虛擬機中並沒有選擇這個算法來管理內存,其最主要的原因是它很難解決對象之間相互循環引用的問題。

2、可達性分析算法

這個算法的基本思想就是通過一系列的稱爲 “GC Roots” 的對象作爲起點,從這些節點開始向下搜索,節點所走過的路徑稱爲引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的。
在這裏插入圖片描述

可作爲GC Roots對象有一下幾種:

  • 虛擬機棧中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中Native方法引用的對象

3、引用

JDK1.2 以後,Java 對引用的概念進行了擴充,將引用分爲強引用、軟引用、弱引用、虛引用四種(引用強度逐漸減弱)

  1. 強引用
    以前我們使用的大部分引用實際上都是強引用,這是使用最普遍的引用。如果一個對象具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當內存空 間不足,Java 虛擬機寧願拋出 OutOfMemoryError 錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。

  2. 軟引用(SoftReference)
    如果一個對象只具有軟引用,那就類似於可有可無的生活用品。如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

  3. 弱引用(WeakReference)
    如果一個對象只具有弱引用,那就類似於可有可無的生活用品。弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它 所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。

  4. 虛引用(PhantomReference)
    "虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。
    虛引用主要用來跟蹤對象被垃圾回收的活動。

特別注意,在程序設計中一般很少使用弱引用與虛引用,使用軟引用的情況較多,這是因爲軟引用可以加速 JVM 對垃圾內存的回收速度,可以維護系統的運行安全,防止內存溢出(OutOfMemory)等問題的產生。

4、不可達的對象並非“非死不可”

即使在可達性分析法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑階段”,要真正宣告一個對象死亡,至少要經歷兩次標記過程;可達性分析法中不可達的對象被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或 finalize 方法已經被虛擬機調用過時,虛擬機將這兩種情況視爲沒有必要執行。
被判定爲需要執行的對象將會被放在一個隊列中進行第二次標記,除非這個對象與引用鏈上的任何一個對象建立關聯,否則就會被真的回收。

5、如何判斷一個常量是廢棄常量,一個類是無用的類?

假如在常量池中存在字符串 “abc”,如果當前沒有任何 String 對象引用該字符串常量的話,就說明常量 “abc” 就是廢棄常量,如果這時發生內存回收的話而且有必要的話,“abc” 就會被系統清理出常量池。

判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面 3 個條件才能算是 “無用的類” :

  1. 該類所有的實例都已經被回收,也就是 Java 堆中不存在該類的任何實例。
  2. 加載該類的 ClassLoader 已經被回收。
  3. 該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

虛擬機可以對滿足上述 3 個條件的無用類進行回收,這裏說的僅僅是“可以”,而並不是和對象一樣不使用了就會必然被回收。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章