GC(垃圾回收) Java_GC詳解

Java_GC詳解

 

Java —— GC

標籤(空格分隔): Javahtml


要想深刻了解Java的GC(Garbage Collection),咱們應該先探尋以下三個問題:java

  • What? -- 哪些內存須要回收?
  • When? -- 何時回收?
  • How? -- 如何回收?

GC Definition

Definition: Program itself finds and collects memory which is useless. It is a form of automatic memory management which doesn't need programmers release memory.
Java中爲何會有GC機制呢?程序員

  • 安全性考慮;-- for security.
  • 減小內存泄露;-- erase memory leak in some degree.
  • 減小程序員工做量。-- Programmers don't worry about memory releasing.

What? -- 哪些內存須要回收?

咱們知道,內存運行時JVM會有一個運行時數據區來管理內存。它主要包括5大部分:程序計數器(Program Counter Register)、虛擬機棧(VM Stack)、本地方法棧(Native Method Stack)、方法區(Method Area)、堆(Heap).面試

而其中程序計數器、虛擬機棧、本地方法棧是每一個線程私有的內存空間,隨線程而生,隨線程而亡。例如棧中每個棧幀中分配多少內存基本上在類結構去誒是哪一個下來時就已知了,所以這3個區域的內存分配和回收都是肯定的,無需考慮內存回收的問題。算法

方法區和堆就不一樣了,一個接口的多個實現類須要的內存可能不同,咱們只有在程序運行期間纔會知道會建立哪些對象,這部份內存的分配和回收都是動態的,GC主要關注的是這部份內存。數組

總而言之,GC主要進行回收的內存是JVM中的方法區
涉及到多線程(指堆)、多個對該對象不一樣類型的引用(指方法區),纔會涉及GC的回收。安全

When? -- 何時回收?

在面試中常常會碰到這樣一個問題(事實上筆者也碰到過):如何判斷一個對象已經死去?markdown

很容易想到的一個答案是:對一個對象添加引用計數器。每當有地方引用它時,計數器值加1;當引用失效時,計數器值減1.而當計數器的值爲0時這個對象就不會再被使用,判斷爲已死。是否是簡單又直觀。然而,很遺憾。這種作法是錯誤的!(面試時可千萬別這樣回答哦,我就是不假思索這樣回答,而後就。。)爲何是錯的呢?事實上,用引用計數法確實在大部分狀況下是一個不錯的解決方案,而在實際的應用中也有很多案例,但它卻沒法解決對象之間的循環引用問題。好比對象A中有一個字段指向了對象B,而對象B中也有一個字段指向了對象A,而事實上他們倆都再也不使用,但計數器的值永遠都不可能爲0,也就不會被回收,而後就發生了內存泄露。。多線程

因此,正確的作法應該是怎樣呢?
在Java,C#等語言中,比較主流的斷定一個對象已死的方法是:可達性分析(Reachability Analysis).
全部生成的對象都是一個稱爲"GC Roots"的根的子樹。從GC Roots開始向下搜索,搜索所通過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈能夠到達時,就稱這個對象是不可達的(不可引用的),也就是能夠被GC回收了。以下圖所示:併發

[可達性算法斷定對象是否可回收][1]

不管是引用計數器仍是可達性分析,斷定對象是否存活都與引用有關!那麼,如何定義對象的引用呢?

咱們但願給出這樣一類描述:當內存空間還夠時,可以保存在內存中;若是進行了垃圾回收以後內存空間仍舊很是緊張,則能夠拋棄這些對象。因此根據不一樣的需求,給出以下四種引用,根據引用類型的不一樣,GC回收時也會有不一樣的操做:

  • 強引用(Strong Reference):Object obj = new Object();只要強引用還存在,GC永遠不會回收掉被引用的對象。
  • 軟引用(Soft Reference):描述一些還有用但非必需的對象。在系統將會發生內存溢出以前,會把這些對象列入回收範圍進行二次回收(即系統將會發生內存溢出了,纔會對他們進行回收。)
  • 弱引用(Weak Reference):程度比軟引用還要弱一些。這些對象只能生存到下次GC以前。當GC工做時,不管內存是否足夠都會將其回收(即只要進行GC,就會對他們進行回收。)
  • 虛引用(Phantom Reference):一個對象是否存在虛引用,徹底不會對其生存時間構成影響。

方法區

What部分咱們已經提到,GC主要回收的是堆和方法區中的內存,而上面的How主要是針對對象的回收,他們通常位於堆內。那麼,方法區中的東西該怎麼回收呢?

關於方法區中須要回收的是一些廢棄的常量無用的類

  1. 廢棄的常量的回收。這裏看引用計數就能夠了。沒有對象引用該常量就能夠放心的回收了。
  2. 無用的類的回收。什麼是無用的類呢?
  • 該類全部的實例都已經被回收。也就是Java堆中不存在該類的任何實例;
  • 加載該類的ClassLoader已經被回收;
  • 該類對應的java.lang.Class對象沒有任何地方被引用,沒法在任何地方經過反射訪問該類的方法。

總而言之,對於堆中的對象,主要用可達性分析判斷一個對象是否還存在引用,若是該對象沒有任何引用就應該被回收。而根據咱們實際對引用的不一樣需求,又分紅了4中引用,每種引用的回收機制也是不一樣的。
對於方法區中的常量和類,當一個常量沒有任何對象引用它,它就能夠被回收了。而對於類,若是能夠斷定它爲無用類,就能夠被回收了。

How? -- 如何回收?

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

分爲兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。
缺點:效率問題,標記和清除兩個過程的效率都不高;空間問題,會產生不少碎片。

複製算法

將可用內存按容量劃分爲大小相等的兩塊,每次只用其中一塊。當這一塊用完了,就將還存活的對象複製到另一塊上面,而後把原始空間所有回收。高效、簡單。
缺點:將內存縮小爲原來的一半。

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

標記過程與標記-清除算法過程同樣,但後面不是簡單的清除,而是讓全部存活的對象都向一端移動,而後直接清除掉端邊界之外的內存。

分代收集(Generational Collection)算法

  • 新生代中,每次垃圾收集時都有大批對象死去,只有少許存活,就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集;
  • 老年代中,其存活率較高、沒有額外空間對它進行分配擔保,就應該使用「標記-整理」或「標記-清理」算法進行回收。

一些收集器

Serial收集器

單線程收集器,表示在它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束。"Stop The World".

ParNew收集器

實際就是Serial收集器的多線程版本。

  • 併發(Parallel):指多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態;
  • 並行(Concurrent):指用戶線程與垃圾收集線程同時執行,用戶程序在繼續運行,而垃圾收集程序運行於另外一個CPU上。

Parallel Scavenge收集器

該收集器比較關注吞吐量(Throughout)(CPU用於用戶代碼的時間與CPU總消耗時間的比值),保證吞吐量在一個可控的範圍內。

CMS(Concurrent Mark Sweep)收集器

CMS收集器是一種以得到最短停頓時間爲目標的收集器。

G1(Garbage First)收集器

從JDK1.7 Update 14以後的HotSpot虛擬機正式提供了商用的G1收集器,與其餘收集器相比,它具備以下優勢:並行與併發;分代收集;空間整合;可預測的停頓等。

本部分主要分析了三種不一樣的垃圾回收算法:Mark-Sweep, Copy, Mark-Compact. 每種算法都有不一樣的優缺點,也有不一樣的適用範圍。而JVM中對垃圾回收器並無嚴格的要求,不一樣的收集器會結合多個算法進行垃圾回收。

內存分配

Java技術體系中所提倡的自動內存管理最終能夠歸結爲自動化的解決2個問題:給對象分配內存以及回收分配給對象的內存

對象優先在Eden分配

大多數狀況下,對象在新生代Eden區分配。當Eden區沒有足夠的內存時,虛擬機將發起一次Minor GC。

  • Minor GC(新生代GC):指發生在新生代的垃圾收集動做,由於Java對象大多都具有朝生夕滅的特性,因此Minor GC發生的很是頻繁。
  • Full GC/Major GC(老年代GC):指發生在老年代的GC,出現了Major GC,常常會伴隨至少一次的Minor GC。

大對象直接進老年代

大對象是指須要大量連續內存空間的Java對象(例如很長的字符串以及數組)。

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

JVM爲每一個對象定義一個對象年齡計數器。

  • 若是對象在Eden出生並經歷過第一次Minor GC後仍然存活,而且可以被Survivor容納,則應該被移動到Survivor空間中,而且年齡對象設置爲1;
  • 對象在Survivor區中每熬過一次Minor GC,年齡就會增長1歲,當它的年齡增長到必定程度(默認爲15歲,可經過參數-XX:MaxTenuringThreshold設置),就會被晉升到老年代中。
  • 要注意的是:JVM並非永遠的要求對象的年齡必須達到MaxTenuringThreshold才能晉升老年代,若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的通常,年齡大於等於該年齡的對象就能夠直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。

空間分配擔保

  • 在發生Minor GC以前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代全部對象總空間,若是這個條件成立,則進行Minor GC是安全的;
  • 若是不成立,則虛擬機會查看HandlePromotionFailure設置值是否容許擔保失敗。若是容許,則急促檢查老年代最大可用連續空間是否大於歷次晉升到老年代對象的平均大小,若是大於,將嘗試着進行一次Minor GC,儘管它是有風險的;
  • 若是小於或者HandePromotionFailure設置爲不容許冒險,則這時要改成進行一次Full GC.

總結

本篇博客主要根據Java的GC原理,從What,When,How三方面對如何進行垃圾回收作了分析。
簡而言之:
What -- 堆和方法區;
When -- 已死的對象(引用沒法可達);
How -- 標記-清除-整理-複製算法。
關於GC問題,緊緊把握住這三個問題,而後進行發散性思惟,即可以很好的掌握這部份內容了。
最後對Java對對象的內存分配策略進行了介紹:新生代Eden區 -- Survivor區 -- 老年代

轉載自http://www.cnblogs.com/little-YTMM/p/5613642.html

相關文章
相關標籤/搜索