學習筆記 | Java 垃圾回收(面試必備)

Java 垃圾回收與垃圾收集器

垃圾回收(Garbage Collection,GC),就是經過垃圾收集器把內存中沒用的對象清理掉。垃圾回收涉及到內容:算法

  • 判斷對象是否已死
  • 選擇垃圾收集算法
  • 選擇垃圾收集的時間
  • 選擇適當的垃圾收集器清理垃圾

判斷對象是否已死

判斷對象是否已死:找出哪些對象是已經死掉的,之後不會再用到的。性能優化

判斷對象是否已死的方法:引用計數算法可達性分析算法多線程

引用計數算法

給每個對象添加一個引用計數器,每當有一個地方引用它時,計數器值加 1;每當有一個地方不在引用它時,計數器值減 1,這樣只要計數器的值不爲 0,就說明還有地方引用它,它就不是無用的對象。併發

這種方法看起來很是簡單,可是目前許多主流的虛擬機都沒有選用這種算法來管理內存,原理就是當某些對象之間互相引用時,沒法判斷出這些對象是否已死。性能

可達性分析算法

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

在 Java 語言中,可做爲 GC Roots 的對象包括下面幾種:優化

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中 JNI (即通常說的 Native 方法)引用的對象

經常使用垃圾回收算法

經常使用的垃圾回收算法有三種:spa

  • 標記-清除算法
  • 複製算法
  • 標記-整理算法
  • 分代收集算法

標記-清除算法(Mark-Sweep)

分爲 標記清除 兩個階段,首先標記出全部須要回收的對象,標記完成後統一回收全部被標記的對象。線程

不足之處:3d

  • 效率問題:標記和清除兩個階段的效率都不高;
  • 空間問題:標記清除以後會產生大量不連續的內存碎片,致使在程序運行過程當中須要分配較大對象的時候,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾回收動做。

複製算法(Copying)

爲了解決 "標記-清除" 算法內存碎片化的缺陷而被提出的算法。根據內存容量將內存劃分爲相等大小的兩塊。每次只使用其中一塊,當這一塊內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。

優勢:效率要高於標記-清除算法,不會產生過多的碎片。在對象存活率較高時要進行較多的複製操做,效率會較低。

缺點:可用內存被壓縮到本來的一半。且存儲對象增多的話,Copying 算法的效率會大大下降。

標記-整理算法(Mark-Compact)

先對可用的對象進行標記,而後全部被標記的對象都向一端移動,最後直接清理掉端邊界之外的內存。

優勢:自帶整理功能,這樣不會產生大量不連續的內存空間,適合老年代的大對象存儲。

分代收集算法(Generational Collection)

分代收集算法是目前大部分 JVM 所採用的方法,其核心思想就是根據對象存活的不一樣生命週期將內存劃分爲不一樣的域(把堆內存分爲新生代和老年代)。老年代的特色是每次垃圾回收只有少許對象須要被回收,新生代的特色是每次垃圾回收時都有垃圾須要被回收,所以能夠根據不一樣區域選擇不一樣的回收算法。

新生代與複製算法

目前大部分 JVM 的 GC 對於新生代都採用 Copying 算法,由於新生代中每次垃圾回收都會回收大部分對象,即要複製的對象比較少,一般並非按照 1:1 來劃分新生代。通常將新生代劃分爲一塊較大的 Eden 區和兩個較小的 Survivor 區(From Survivor、To Survivor),每次使用 Eden 區和其中一塊 Survivor 區,當進行回收的時候,將該兩塊空間中還存活的對象複製到另一塊 Survivor 空間中。

老年代與標記複製算法

老年代由於每次只會回收少許對象,於是採用 Mark-Compact 算法

  1. Java 虛擬機提到過的處於方法區的永生代,它用來存儲 class 類、常量、方法描述等,對永生代的回收主要包括廢棄常量和無用的類。
  2. 對象的內存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Space 目前存放對象的那一塊),少數狀況會直接分配到老年代。
  3. 當新生代的 Eden 區和 From Survivor 區空間不足時會觸發一次 MinorGC,進行 GC 後,Eden 區和 From Survivor 區的存活對象會被挪到 To Survivor,而後將 Eden 區和 From Survivor 進行清理。
  4. 若是 To Survivor 沒有足夠存儲某個對象,則將這個對象存儲到老年代。
  5. 在進行 GC 後,使用的即是 Eden 區和 To Survivor區了,如此反覆循環。
  6. 當對象在 Survivor 區躲過一次 GC 後,其年齡就會加 1。默認狀況下年齡達到 15 的對象會被移到老年代中。

垃圾收集器

常見垃圾收集器

如今常見的垃圾收集器有以下幾種:

  • 新生代收集器:Serial、ParNew、Parallel Scavenge
  • 老年代收集器:Serial Old、CMS、Parallel Old
  • 堆內存垃圾收集器:G1

HosSpot 虛擬機的垃圾收集器

Serial 收集器

Serial 收集器是最基本、發展歷史最悠久的收集器。Serial 收集器是一個單線程的收集器,可是這個"單線程"的意義並不只僅說明它只會使用一個 CPU 或一條收集線程去完成垃圾收集工做,更重要的是在它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束(Stop The World)。

Serial 收集器是虛擬機運行在 Client 模式下的默認新生代收集器。

優勢:簡單而高效,對於限定單個 CPU 的環境來講,Serial 收集器沒有線程交互的開銷,專心作垃圾收集能夠得到最高的單線程收集效率。

適用場景:適合運行在 Client 模式下的虛擬機。

ParNew 收集器

ParNew 收集器是 Serial 收集器的多線程版本,可使用多條線程進行垃圾收集。

ParNew 是運行在 Server 模式下的虛擬機中首選的新生代收集器,只有 ParNew 收集器可以與 CMS 收集器配合工做。

ParNew 默認開啓的收集線程數與 CPU 的數量相同,在 CPU 很是多的環境下,可使用 -XX:ParallelGCThreads參數來限制垃圾收集的線程數。

Parallel Scavenge

Parallel Scavenge 是一個新生代收集器,使用複製算法實現,並行的多線程收集器,吞吐量優先的收集器。

Parallel Scavenge 收集器的目標是 達到一個可控制的吞吐量(Throughput)。

Parallel Scavenge 收集器提供了兩個參數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間的 -XX:MaxGCPauseMillis 參數以及設置吞吐大小的 -XX:GCTimeRatio 參數。

MaxGCPauseMills:容許的值是一個大於 0 的毫秒數,收集器將盡量地保證內存回收話費的時間不超過設定值。

GCTimeRatio:參數的值是一個大於 0 且小於 100 的證書,就是垃圾收集時間佔總時間的比率,至關因而吞吐量的倒數。

Serial Old 收集器

Serial Old 是 Serial 收集器的老年代版本,單線程收集器,使用"標記-整理"算法。

適合 Client 模式下的虛擬機使用。

在 Server 模式下,還有兩大用途:

  • 在 JDK1.5 以及之前的版本中與Parallel Scavenge 收集器搭配使用
  • 做爲 CMS 收集器的後備預案,在併發收集發生 Concurrent Mode Failure 時使用。

Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多線程和"標記-整理"算法。從 JDK 1.6 開始提供。 在注重吞吐量以及 CPU 資源敏感的場合,均可以優先考慮 Parallel Scavenge 加 Parallel Old 收集器。

CSM 收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS 收集器是基於"標記-清除"算法實現的,它的運做過程相對於前面幾種收集器來講更復雜一些,整個過程分爲 4 個步驟:

  1. 初始標記
  2. 併發標記
  3. 從新標記
  4. 併發清除

CMS 是一款優秀的收集器,主要優勢:併發收集、低停頓。可是 CMS 還存在如下 3 個缺點:

  • CMS 收集器對 CPU 資源很是敏感。
  • CMS 收集器沒法處理浮動垃圾(Floating Garbage)。
  • CMS 基於"標記-清除"算法實現,會產生大量空間碎片。

G1 收集器

G1(Garbage First) 收集器是當今收集器技術發展的最前沿成果之一。

G1 收集器是基於 標記-整理 算法實現的收集器,它不會產生空間碎片,能夠很是精確地控制停頓。

G1 是一款 面向服務端應用 的垃圾收集器。與其餘 GC 收集器相比,G1 具有以下特色:

  • 並行與併發:G1 能充分利用多 CPU、多核環境下的硬件優點,使用多個 CPU 來縮短 Stop-The-World 停頓時間,部分其餘收集器本來須要停頓 Java 線程執行的 GC 動做,G1 收集器仍然能夠經過併發的方式讓 Java 程序繼續執行。
  • 分代收集:與其餘收集器同樣,分代概念在 G1 中依然得以保留。雖然 G1 能夠不須要其餘收集器配合就能獨立管理整個 GC 堆,但它可以採用不一樣的方式去處理新建立的對象和已經存活了一段時間、熬過屢次 GC 的舊對象以獲取更好的收集效果。
  • 空間整合:G1 從總體上來看是基於"標記-整理"算法實現,從局部上看是基於"複製"算法實現的,這兩種算法都意味着 G1 運做期間不會產生內存空間碎片,收集後能提供規整的可用內存。這種特性有利用程序長時間運行,分配大對象時不會由於沒法找到連續內存空間而提早出發下一次 GC。
  • 可預測的停頓:這是 G1 相對於 CMS 的另外一大優點,下降停頓時間是 G1 和 CMS 共同的關注點,但 G1 除了追求低停頓外,還能創建可預測的停頓時間模型,能讓使用者明確指定在一個長度爲 M 毫秒的時間片斷內,消耗在垃圾收集上的時間不得超過 N 毫秒,這幾乎是實時 Java(RTSJ)的垃圾收集器的特徵了。

垃圾收集器參數總結

參數 描述
UseSerialGC 虛擬機運行在 Client 模式下的默認值,打開此開關後,使用 Serial + Serial Old 的收集器組合進行內存回收
UseParNewGC 使用 ParNew + Serial Old 收集器組合進行內存回收
UseConcMarkSweepGC 使用 ParNew + CMS + Serial Old 的收集器組合進行內存回收。Serial Old 收集器將做爲CMS收集器出現 Concurrent Mode Failure 失敗後的後備收集器使用
UseParallelGC 虛擬機運行在 Server 模式下的默認值,使用 Parallel Scavenge + Serial Old(PS MarkSweep)的收集器組合進行內存回收
UseParallelOldGC 使用 Parallel Scavenge + Parallel Old 的收集器組合進行內存回收
SurvivorRatio 新生代中 Eden 區域與 Survivor 區域的容量比值,默認爲8,表明Eden:From:To=8:1:1
PretenureSizeThreshold 直接晉升到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配
MaxTenuringThreshold 晉升到老年代的年齡。每一個對象在堅持過一次 Minor GC 以後,年齡就加 1,當超過這個參數值時就進入老年代
UseAdaptiveSizePolicy 動態調整 Java 堆中各個區域的大小以及進入老年代的年齡
HandlePromotionFailure 是否容許分配擔保失敗,即老年代的剩餘空間不足以應對新生代的整個 Eden 和 Survivor 區的全部對象都存活的極端狀況
ParallelGCThreads 設置並行 GC 時進行內存回收的線程數
GCTimeRatio GC 時間佔總時間的比率,默認爲99,即容許 1% 的 GC 時間,僅在使用 Parallel Scavenge 收集器時生效。
MaxGCPauseMillis 設置 GC 的最大停頓時間。僅在使用 Parallel Scavenge 收集器時生效
CMSInitiationOccupancyFraction 設置 CMS 收集器在老年代空間誒使用多少後觸發垃圾收集。默認值爲68%,僅在使用 CMS 收集器時生效
UseCMSCompactAtFullCollection 設置 CMS 收集器在完成垃圾收集後是否要進行一次內存碎片整理。僅在使用 CMS 收集器時生效
CMSFullGCsBeforeCompaction 設置 CMS 收集器在進行若干次垃圾收集後再啓動一次內存碎片整理。僅在使用 CMS 收集器時生效

內存分配與回收策略

  1. 對象優先在 Eden 分配:大多數狀況下,對象在新生代 Eden 區中分配。當 Eden 區沒有足夠的空間進行分配時,虛擬機將發起一次 Minor GC。
  2. 大對象直接進入老年代:大對象是指須要大量連續內存空間的 Java 對象,虛擬機提供了一個 -XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代中分配。這樣作的目的是避免在 Eden 區及兩個 Survivor 區以前發生大量的內存拷貝。
  3. 長期存活的對象將進入老年代:若是對象在 Eden 出生並通過第一次 Minor GC 後仍然存活,而且能被 Survivor 容納的話,將被移動到 Survivor 空間中,並將對象年齡設置爲 1,對象在 Survivor 區中每熬過一次 Minor GC,年齡增長 1 歲,當它的年齡增長到必定程度(默認15歲)時,就會被晉升到老年代中。
  4. 動態對象年齡斷定:爲了更好的適應不一樣程序的內存狀況,虛擬機不老是須要對象年齡必須達到 MaxTenuringThreshold 才能晉升到老年代,若是在 Survivor 空間中相同年齡全部對象大小的總和大於 Survivor 空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無須等待 MaxTenuringThreshold 中要求的年齡。
  5. 空間分配擔保:在發生 Minor GC 時,虛擬機會檢測以前每次晉升到老年代的平均大小是否大於老年代的賽過於空間大小,若是大於,則改成直接進行一次 Full GC;若是小於,則查看 HandlePromotionFailure 設置是否容許擔保失敗,若是容許,那隻會進行 Minor GC,若是不容許,則也要改成進行一次 Full GC。

Minor GC vs Major GC vs Full GC

Minor GC:指發生在新生代(包括 Eden 區和 Survivor 區)的垃圾收集動做,由於 Java 對象大多都具有朝生夕滅的特性,因此 Minor GC 很是頻繁,通常回收速度也比較快。

Major GC:指發生在老年代的 GC,出現了 Major GC,常常會伴隨至少一次 Minor GC(但非絕對的,在 Parallel Scavenge 收集器的收集策略裏就有直接進行 Major GC 的策略選擇過程)。Minor GC 的速度通常會比 Minor GC 的速度慢 10 倍以上。

Full GC:針對新生代、老年代、元空間(Metaspace、Java8 以上版本取代 Perm gen)的全局範圍的 GC。Full GC 不等於 Major GC,也不等於 Minor GC + Major GC,發生 Full GC 須要看使用了什麼垃圾收集器組合,才能解釋是什麼樣的垃圾回收。

關注得到更多分享


推薦推薦:

相關文章
相關標籤/搜索