03 JVM的垃圾回收機制

一、前言

理解JVM的垃圾回收機制(簡稱GC)有什麼好處呢?做爲一名軟件開發者,知足本身的好奇心將是一個很好的理由,不過更重要的是,理解GC工做機制能夠幫助你寫出更好的Java程序。java

在學習GC前,你應該知道一個技術名詞:「stop-the-world」 ,不管你選擇哪一種GC算法,「stop-the-world」都會發生。「stop-the-world」意味着JVM中止應用程序,而去進行垃圾回收。當「stop-the-world」發生時,除了進行垃圾回收的線程,其餘全部線程都將中止運行。被中斷的任務將在GC任務完成後恢復執行。GC調優每每意味着減小「stop-the-world」的時間。git

二、分代垃圾收集機制

在HotSpot虛擬機中,將內存分爲 年輕代(young generation)、老年代(old generation) 和 永久代(permanent generation)github

咱們看一下這幅圖:算法

年輕代: 新建立的對象都存放在這裏。由於大多數對象很快變得不可達,因此大多數對象在年輕代中建立,而後消失。當對象從這塊內存區域消失時,咱們說發生了一次「minor GC」。服務器

老年代: 沒有變得不可達,存活下來的年輕代對象被複制到這裏。這塊內存區域通常大於年輕代。由於它更大的規模,GC發生的次數比在年輕代的少。對象從老年代消失時,咱們說「major GC」(或「full GC」)發生了。框架

永久代: 永久代(permanent generation) 也稱爲「方法區(method area)」,它存儲class對象和字符串常量。因此這塊內存區域絕對不是永久的存放從老年代存活下來的對象的。發生在這裏的垃圾回收也被稱爲major GC。函數

三、垃圾收集算法

垃圾收集算法以下:性能

  • 年輕代-複製算法
  • 老年代-標記清除算法
  • 老年代-標記整理算法
  • 永久代-方法區回收

年輕代-複製算法

該算法的核心是將可用內存按容量劃分爲大小相等的兩塊,每次只用其中一塊,當這一塊的內存用完,就將還存活的對象複製到另一塊上面,而後把已使用過的內存空間一次清理掉。這使得每次只對其中一塊內存進行回收,分配也就不用考慮內存碎片等複雜狀況,實現簡單且運行高效。以下圖:學習

老年代-標記清除算法

該算法分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象(可達性分析),在標記完成後統一清理掉全部被標記的對象。以下圖:命令行

該算法會有如下兩個問題:

  1. 效率問題:標記過程和清除過程的效率都不高;
  2. 空間問題:標記清除後會產生大量不連續的內存碎片,空間碎片太多可能會致使在運行過程當中須要分配較大對象時沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集。

老年代-標記整理算法

標記清除算法會產生內存碎片,而複製算法須要有額外的內存擔保空間,因而針對老年代的特色,又有了標記整理算法。標記整理算法的標記過程與標記清除算法相同,但後續步驟再也不對可回收對象直接清理,而是讓全部存活的對象都向一端移動,而後清理掉這一端邊界之外的內存。以下圖:

永久代-方法區回收

在方法區進行垃圾回收通常「性價比」較低,由於在方法區主要回收兩部份內容:廢棄常量無用的類

回收廢棄常量與回收其餘年代中的對象相似。

可是判斷一個類是否是無用的類的條件則至關苛刻:

  • 該類全部的實例都已經被回收,Java堆中不存在該類的任何實例;
  • 該類對應的Class對象沒有在任何地方被引用;
  • 加載該類的ClassLoader已經被回收;

但即便知足以上條件也未必必定會回收,Hotspot VM還提供了-Xnoclassgc參數控制(關閉CLASS的垃圾回收功能)。所以在大量使用動態代理、CGLib等字節碼框架的應用中必定要關閉該選項,開啓VM的類卸載功能,以保證方法區不會溢出。

四、垃圾回收的兩個重要方法

System.gc()方法 和 finalize()方法

System.gc()方法

使用System.gc() 能夠無論JVM使用的是哪種垃圾回收的算法,均可以請求Java的垃圾回收。在命令行中有一個參數-verbosegc能夠查看Java使用的堆內存的狀況,它的格式以下:java -verbosegc classfile 因爲這種方法會影響系統性能,不推薦使用,因此不詳細介紹。

finalize()方法

JVM垃圾回收器在回收一個對象以前,通常要求程序調用適當的方法釋放資源,但在沒有明確釋放資源的狀況下,Java提供了缺省機制來終止該對象從而釋放資源,這個方法就是finalize() 。它的原型爲:protected void finalize() throws Throwable 在finalize() 方法返回以後,對象消失,垃圾收集開始執行。原型中的throws Throwable 表示它能夠拋出任何類型的異常。

之因此要使用finalize(),是由於存在着垃圾回收器不能處理的特殊狀況。例如:

  1. 因爲在分配內存的時候可能採用了相似 C語言的作法,而非Java一般的new 。這種狀況主要發生在 native 方法中,好比 native 方法調用了C/C++malloc()函數來分配存儲空間,除非調用 free() 函數,不然這些內存空間將不會獲得釋放,那麼這個時候就可能形成內存泄漏。可是因爲 free() 是C/C++中的函數,因此 finalize() 中能夠用本地方法來調用它。以釋放這些「特殊」的內存空間。

  2. 或者是打開的文件資源,這些資源不屬於垃圾回收器的回收範圍。

五、觸發GC(Garbage Collector)的條件

  1. GC在優先級最低的線程中運行,通常在應用程序空閒即沒有應用線程在運行時被調用。但下面的條件例外。

  2. Java堆內存不足時,GC會被調用。當應用線程在運行,並在運行過程當中建立新對象,若這時內存空間不足,JVM就會強制調用GC線程。若GC一次以後仍不能知足內存分配,JVM會再進行兩次GC,若仍沒法知足要求,則JVM將報「out of memory」的錯誤,Java應用將中止。

六、減小GC開銷的措施

  1. 不要顯式調用System.gc() 。此函數建議JVM進行主GC,雖然只是建議而非必定,但不少狀況下它會觸發主GC,從而增長主GC的頻率,也即增長了間歇性停頓的次數。大大的影響系統性能。

  2. 儘可能減小臨時對象的使用。臨時對象在跳出函數調用後,會成爲垃圾,少用臨時變量就至關於減小了垃圾的產生,從而延長了出現上述第二個觸發條件出現的時間,減小了主GC的機會。

  3. 對象不用時最好顯式置爲Null。通常而言,爲Null的對象都會被做爲垃圾處理,因此將不用的對象顯式地設爲Null,有利於GC收集器斷定垃圾,從而提升了GC的效率。

  4. 儘可能使用StringBuffer,而不用String來累加字符串。因爲String是固定長的字符串對象,累加String對象時,並不是在一個String對象中擴增,而是從新建立新的String對象,如 Str5=Str1+Str2+Str3+Str4; 這條語句執行過程當中會產生多個垃圾對象,由於每次做「+」操做時都必須建立新的String對象,但這些過渡對象對系統來講是沒有實際意義的,只會增長更多的垃圾。避免這種狀況能夠改用StringBuffer來累加字符串,因StringBuffer是可變長的,它在原有基礎上進行擴增,不會產生中間對象。

  5. 能用基本類型如 int, long 就不用包裝類型Integer, Long。基本類型變量佔用的內存資源比包裝類型佔用的少得多,若是沒有必要,最好使用基本變量。

  6. 儘可能少用靜態對象變量。靜態變量屬於全局變量,不會被GC回收,它們會一直佔用內存。

  7. 分散對象建立或刪除的時間。集中在短期內大量建立新對象,特別是大對象,會致使忽然須要大量內存,JVM在面臨這種狀況時,只能進行主GC,以回收內存或整合內存碎片,從而增長主GC的頻率。集中刪除對象,道理也是同樣的。它使得忽然出現了大量的垃圾對象,空閒空間必然減小,從而大大增長了下一次建立新對象時強制主GC的機會。

七、幾種垃圾收集器

在JDK7中,有5種垃圾收集器:

  • Serial收集器
  • Parallel收集器
  • Parallel Old收集器 (Parallel Compacting GC)收集器
  • Concurrent Mark & Sweep GC (or 「CMS」)收集器
  • Garbage First (G1) 收集器

其中,Serial 收集器必定不能用於服務器端。這個收集器類型僅應用於單核CPU桌面電腦。使用Serial收集器會顯着下降應用程序的性能。


本文永久更新地址:https://github.com/nnngu/LearningNotes/blob/master/JVM/03%20JVM%E7%9A%84%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6.md

相關文章
相關標籤/搜索