JVM-垃圾回收機制

定義

垃圾回收機制是一種在堆內存中找出哪些對象在被使用,還有哪些對象沒被使用,而且將後者刪掉的機制。算法

所謂使用中的對象(已引用對象),指的是程序中有指針指向的對象;而未使用中的對象(未引用對象),則沒有被任何指針給指向,所以佔用的內存也能夠被回收掉。數組

垃圾回收機制

eden->s0("from")->s1("to")->tentired緩存

上圖所示的 eden 區、s0("From") 區、s1("To") 區都屬於新生代,tentired 區屬於老年代。大部分狀況,對象都會首先在 Eden 區域分配,在一次新生代垃圾回收後,若是對象還存活,則會進入 s1("To"),而且對象的年齡還會加 1(Eden 區->Survivor 區後對象的初始年齡變爲 1),當它的年齡增長到必定程度(默認爲 15 歲),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,能夠經過參數 -XX:MaxTenuringThreshold 來設置。通過此次GC後,Eden區和"From"區已經被清空。這個時候,"From"和"To"會交換他們的角色,也就是新的"To"就是上次GC前的「From」,新的"From"就是上次GC前的"To"。無論怎樣,都會保證名爲To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到「To」區被填滿,"To"區被填滿以後,會將全部對象移動到年老代中。安全

Java自動內存管理機制

Java的自動內存管理主要是針對對象內存的回收和對象內存的分配。併發

同時,Java自動內存管理最核心的功能是 堆內存中對象的分配與回收優化

堆內存常見分配策略

  • 對象優先在 eden 區分配
  • 大對象直接進入老年代
  • 長期存活的對象將進入老年代

對象優先在 eden 區分配

目前主流的垃圾收集器都會採用分代回收算法,所以須要將堆內存分爲新生代和老年代,這樣咱們就能夠根據各個年代的特色選擇合適的垃圾收集算法。線程

大多數狀況下,對象在新生代中eden區分配。當eden區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC.設計

大對象直接進入老年代

大對象就是須要大量連續內存空間的對象(好比:字符串、數組)。指針

爲了不爲大對象分配內存時因爲分配擔保機制帶來的複製而下降效率。

長期存活的對象將進入老年代

既然虛擬機採用了分代收集的思想來管理內存,那麼內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。爲了作到這一點,虛擬機給每一個對象一個對象年齡(Age)計數器

若是對象在 Eden 出生並通過第一次 Minor GC 後仍然可以存活,而且能被 Survivor 容納的話,將被移動到 Survivor 空間中,並將對象年齡設爲 1,對象在 Survivor 中每熬過一次MinorGC,年齡就增長1歲,當它的年齡增長到必定程度(默認爲 15 歲),就會被晉升到老年代中。

動態對象年齡斷定

爲了更好的適應不一樣程序的內存狀況,虛擬機不是永遠要求對象年齡必須達到了某個值才能進入老年代,若是 Survivor 空間中相同年齡全部對象大小的總和大於 Survivor 空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無需達到要求的年齡。

MinorGC和FullGC區別

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

如何判斷對象已經死亡

  • 引用計數法
  • 可達性分析算法

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

引用計數法

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

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

可達性分析算法

這個算法的基本思想就是經過一系列的稱爲 「GC Roots」 的對象做爲起點,從這些節點開始向下搜索,節點所走過的路徑稱爲引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連的話,則證實此對象是不可用的。

GC Roots對象

在JAVA中能夠被做爲GC Roots的對象主要是:

  • 一、虛擬機棧中引用的對象。
  • 二、方法區中類靜態屬性引用的對象。
  • 三、方法區中常量引用的對象。
  • 四、本地方法棧中JNI引用的對象。

不可達的對象並不是「非死不可」

即便在可達性分析法中不可達的對象,也並不是是「非死不可」的,這時候它們暫時處於「緩刑階段」,要真正宣告一個對象死亡,至少要經歷兩次標記過程;可達性分析法中不可達的對象被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或 finalize 方法已經被虛擬機調用過期,虛擬機將這兩種狀況視爲沒有必要執行。

被斷定爲須要執行的對象將會被放在一個隊列中進行第二次標記,除非這個對象與引用鏈上的任何一個對象創建關聯,不然就會被真的回收。

再談引用

不管是經過引用計數法判斷對象引用數量,仍是經過可達性分析法判斷對象的引用鏈是否可達,斷定對象的存活都與「引用」有關。

JDK1.2 以前,Java 中引用的定義很傳統:若是 reference 類型的數據存儲的數值表明的是另外一塊內存的起始地址,就稱這塊內存表明一個引用。

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

強引用

只要有一個引用存在,永遠都不會被回收掉

之前咱們使用的大部分引用實際上都是強引用,這是使用最廣泛的引用。若是一個對象具備強引用,那就相似於必不可少的生活用品,垃圾回收器毫不會回收它。當內存空間不足,Java 虛擬機寧願拋出 OutOfMemoryError 錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足問題

軟引用(SoftReference)

通常是指還有用,可是非必須的對象。在內存空間不足的狀況下,會回收掉此部份內存,若是還不夠則會拋出內存溢出異常。

若是一個對象只具備軟引用,那就相似於無關緊要的生活用品。若是內存空間足夠,垃圾回收器就不會回收它,若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高速緩存。

軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收,JAVA虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。

弱引用(WeakReference)

通常指非必須的對象,比軟引用還要弱,它只能生存到下一次垃圾回收前,若是一旦發生垃圾回收,它將會被回收掉。

若是一個對象只具備弱引用,那就相似於無關緊要的生活用品。弱引用與軟引用的區別在於:只具備弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它 所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程, 所以不必定會很快發現那些只具備弱引用的對象。

弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

虛引用(PhantomReference)

最弱的引用關係,沒法經過虛引用來取得一個對象的實例。爲一個對象設置虛引用的惟一目的就是可以在這個對象被回收的時候收到一個系統通知。

"虛引用"顧名思義,就是形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收。

虛引用主要用來跟蹤對象被垃圾回收的活動.

虛引用必須和引用隊列(ReferenceQueue)聯合使用

虛引用與軟引用和弱引用的一個區別

虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃 圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。程序能夠經過判斷引用隊列中是 否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序若是發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要的行動。

特別注意,在程序設計中通常不多使用弱引用與虛引用,使用軟引用的狀況較多,這是由於軟引用能夠加速JVM對垃圾內存的回收速度,能夠維護系統的運行安全,防止內存溢出(OutOfMemory)等問題的產生

比較常見的將對象斷定爲可回收對象的狀況

1.顯示地將某個引用賦值爲null或者將已經指向某個對象的引用指向新的對象

好比下面的代碼:

Object obj = new Object();
obj = null;
Object obj1 = new Object();
Object obj2 = new Object();
obj1 = obj2;

2.局部引用所指向的對象

好比下面這段代碼:

void fun() {
 
.....
    for(int i=0;i<10;i++) {
        Object obj = new Object();
        System.out.println(obj.getClass());
    }   
}

循環每執行完一次,生成的Object對象都會成爲可回收的對象。

3.只有弱引用與其關聯的對象

WeakReference<String> wr = new WeakReference<String>(new String("world"));

永久代的垃圾回收

注意,在堆區以外還有一個代就是永久代(Permanet Generation),它用來存儲class類、常量、方法描述等。對永久代的回收主要回收兩部份內容:廢棄常量和無用的類

如何判斷一個常量是廢棄常量

運行時常量池主要回收的是廢棄的常量。那麼,咱們如何判斷一個常量是廢棄常量呢?

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

注意: JDK1.7 及以後版本的JVM已經將運行時常量池從方法區中移了出來,在 Java 堆(Heap)中開闢了一塊區域存放運行時常量池

如何判斷一個類是無用的類

方法區主要回收的是無用的類,那麼如何判斷一個類是無用的類的呢?

斷定一個常量是不是「廢棄常量」比較簡單,而要斷定一個類是不是「無用的類」的條件則相對苛刻許多。類須要同時知足下面 3 個條件才能算是 「無用的類」 :

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

虛擬機能夠對知足上述3個條件的無用類進行回收,這裏說的僅僅是「能夠」,而並非和對象同樣不使用了就會必然被回收。

一些概念

Stop the world 概念

JVM因爲要執行GC而中止了應用程序的執行,而且這種情形會在任何一種GC算法中發生。當Stop-the-world發生時,除了GC所需的線程之外,全部線程都處於等待狀態直到GC任務完成。事實上,GC優化不少時候就是指減小Stop-the-world發生的時間,從而使系統具備 高吞吐 、低停頓 的特色。

由於垃圾回收的時候,須要整個的引用狀態保持不變,不然斷定是斷定垃圾,等我稍後回收的時候它又被引用了,這就全亂套了。因此,GC的時候,其餘全部的程序執行處於暫停狀態,卡住了。
幸運的是,這個卡頓是很是短(尤爲是新生代),對程序的影響微乎其微 (關於其餘GC好比並發GC之類的,在此不討論)。
因此GC的卡頓問題由此而來,也是情有可原,暫時無可避免。

並行和併發概念

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

  • 併發(Concurrent):指用戶線程與垃圾收集線程同時執行(但不必定是並行,可能會交替執行),用戶程序在繼續運行,而垃圾收集器運行在另外一個 CPU 上。

吞吐量

吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間 /(運行用戶代碼時間 + 垃圾收集時間)。 虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

相關文章
相關標籤/搜索