1. 程式人生 > >[深入理解JVM 七]---Jvm垃圾回收機制

[深入理解JVM 七]---Jvm垃圾回收機制

本篇部落格大部分內容來自《深入理解java虛擬機器》,也參考了http://jbutton.iteye.com/blog/1569746這篇文章的部分內容,這裡註明出處。這篇部落格也是這個系列的第二篇,在這篇部落格裡我會對java的記憶體回收機制做個詳細的整理。希望通過書寫這個系列的部落格能讓自己對Java底層執行過程有個詳細的瞭解,花費了一整天的時間終於有了一個詳細的瞭解了—TML

概述

接下來的全文流程會按照四個部分來解釋垃圾回收機制,做一個全面總結。
第一部分:基本介紹垃圾回收機制的目標,執行時間和方式等。
第二部分:一些成熟的垃圾回收演算法
第三部分:一些成熟的垃圾收集器
第四部分:垃圾回收策略有哪些

基本介紹

首先提出三個問題:1,哪些記憶體需要回收? 2,什麼時候回收? 3,如何回收?然後進行簡單的回答:

1,哪些記憶體需要回收?

回收區域主要集中在java堆和方法區

程式計數器、虛擬機器棧、本地方法棧3個區域隨執行緒而生,隨執行緒而滅;棧中的棧幀隨著方法的進入和退出而有條不紊地執行著出棧和入棧操作。每一個棧幀中分配多少記憶體基本上是在類結構確定下來時就已知的,因此這幾個區域的記憶體分配和回收都具備確定性,所以不需要考慮回收,而Java堆和方法區則不一樣,一個介面中的多個實現類需要的記憶體可能不一樣,一個方法中的多個分支需要的記憶體也可能不一樣,我們只有在程式處於執行期間時才能知道會建立哪些物件,這部分記憶體的分配和回收都是動態的,垃圾收集器所關注的是這部分記憶體。

2,什麼時候回收

1、 物件沒有引用
2、 作用域發生未捕獲異常
3、 程式在作用域正常執行完畢
4、 程式執行了System.exit()
5、 程式發生意外終止(被殺程序等)

3,如何回收

引用計數演算法

原理

在JDK1.2之前,使用的是引用計數器演算法,即當這個類被載入到記憶體以後,就會產生方法區,堆疊、程式計數器等一系列資訊,當建立物件的時候,為這個物件在堆疊空間中分配物件,同時會產生一個引用計數器,同時引用計數器+1,當有新的引用的時候,引用計數器繼續+1,而當其中一個引用銷燬的時候,引用計數器-1,當引用計數器被減為零的時候,標誌著這個物件已經沒有引用了,可以回收了!

問題

當我們的程式碼出現下面的情形時,該演算法將無法適應
a) ObjA.obj = ObjB
b) ObjB.obj - ObjA

這裡寫圖片描述

這樣的程式碼會產生如下引用情形 objA指向objB,而objB又指向objA,這樣當其他所有的引用都消失了之後,objA和objB還有一個相互的引用,也就是說兩個物件的引用計數器各為1,而實際上這兩個物件都已經沒有額外的引用,已經是垃圾了。

可達性分析演算法

原理

這個演算法的基本思路就是通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個物件不達)時,則證明此物件是不可用的

這裡寫圖片描述

可作為GC root的物件

1,虛擬機器棧(棧幀中的本地變量表)中引用的物件。
2,方法區中類靜態屬性引用的物件。
3,方法區中常量引用的物件。
//4,本地方法棧中JNI(即一般說的Native方法)引用的物件。

如何逃逸

即使在可達性分析演算法中不可達的物件,也並非是“非死不可”的,這時候它們暫時處於“緩刑”階段,要真正宣告一個物件死亡,至少要經歷兩次標記過程:如果物件在進行可達性分析後發現沒有與GC Roots相連線的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此物件是否有必要執行finalize()方法。當物件沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機器呼叫過,虛擬機器將這兩種情況都視為“沒有必要執行”

第一次標記:如果物件在進行可達性分析後發現沒有與GC Roots相連線的引用鏈,那它將會被第一次標記並且進行一次篩選。如果這個物件被判定為有必要執行finalize()方法,那麼這個物件將會放置在一個叫做F-Queue的佇列之中,並在稍後由一個由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行它。這裡所謂的“執行”是指虛擬機器會觸發這個方法,但並不承諾會等待它執行結束,這樣做的原因是,如果一個物件在finalize()方法中執行緩慢,或者發生了死迴圈(更極端的情況),將很可能會導致F-Queue佇列中其他物件永久處於等待,甚至導致整個記憶體回收系統崩潰。finalize()方法是物件逃脫死亡命運的最後一次機會。
第二次標記,GC將對F-Queue中的物件進行第二次小規模的標記,如果物件要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個物件建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變數或者物件的成員變數,那在第二次標記時它將被移除出“即將回收”的集合;如果物件這時候還沒有逃脫,那基本上它就真的被回收了。

注意:finalize()方法只能被系統呼叫一次,如果這次物件逃逸成功,下一次再次被回收時候就無法自救了。

=====================TML=============TML===============TTML==========

瞭解了以上部分的內容,就會對垃圾回收演算法有個大概的概念,可以得知,其實物件是否要被垃圾回收,最核心的問題還是有沒有引用指向它。只要有引用一直指向該物件,無論該物件是否有用都不會被回收,所以引用是關鍵。我們希望能描述這樣一類物件:當記憶體空間還足夠時,則能保留在記憶體之中;如果記憶體空間在進行垃圾收集後還是非常緊張,則可以拋棄這些物件。很多系統的快取功能都符合這樣的應用場景。

四類引用

1.2之後,Java對引用的概念進行了擴充,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(PhantomReference)4種

強引用(最強)

強引用就是指在程式程式碼之中普遍存在的,類似“Object obj=new Object()”這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的物件。

總結起來就是一直存在,超過記憶體就報錯,報錯就報錯,我就是一直在!

軟引用(第二強)

軟引用是用來描述一些還有用但並非必需的物件。對於軟引用關聯著的物件,在系統將要發生記憶體溢位異常之前,將會把這些物件列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會丟擲記憶體溢位異常

總結起來就是我很想存在,除非即將發生記憶體溢位,為了大局,就算被引用也請將我回收吧!

弱引用(第三強)

弱引用也是用來描述非必需物件的,但是它的強度比軟引用更弱一些,被弱引用關聯的物件只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件

總結起來就是雖然我很想存在,但我好像沒那麼必要哈,直接把我回收吧!

虛引用(最弱)

虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關係。一個物件是否有虛引
用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個物件例項。為一個物件設定虛引用關聯的唯一目的就是能在這個物件被收集器回收時收到一個系統通知

總結起來就是有我沒我都一樣,回收的時候我告訴您一聲。

=====================TML=============TML===============TTML==========

上邊談完了關於物件的回收,主要是在java堆裡的回收,那麼在方法區裡又是怎麼回收的呢

方法區的回收

回收的主要內容

永久代的垃圾收集主要回收兩部分內容:廢棄常量無用的類

回收廢棄常量

回收廢棄常量與回收Java堆中的物件非常類似。以常量池中字面量的回收為例,假如一個字串“abc”已經進入了常量池中,但是當前系統沒有任何一個String物件是叫做“abc”的,換句話說,就是沒有任何String物件引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果這時發生記憶體回收,而且必要的話,這個“abc”常量就會被系統清理出常量池。常量池中的其他類(介面)、方法、欄位的符號引用也與此類似。

回收無用類

判定一個類是否是“無用的類”的條件有三個:
1,類所有的例項都已經被回收,也就是Java堆中不存在該類的任何例項
2,載入該類的ClassLoader已經被回收
3,該類對應的java.lang.Class物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
虛擬機器可以對滿足上述3個條件的無用類進行回收,這裡說的僅僅是“可以”,而並不是
和物件一樣,不使用了就必然會回收。是否對類進行回收,HotSpot虛擬機器提供了-Xnoclassgc引數進行控制,還可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading檢視類載入和解除安裝資訊,

可能發生場景

在大量使用反射、動態代理、CGLib等ByteCode框架、動態生成JSP以及OSGi這類頻繁
自定義ClassLoader的場景都需要虛擬機器具備類解除安裝的功能,以保證永久代不會溢位。

Hotpot的演算法實現

列舉根節點

要進行可達性分析,首先要找到GCroot,可以作為GC Roots的節點主要就是一些全域性性的引用(例如常量或者靜態屬性)或者棧幀中的本地變量表。而我們做可達性分析的時候,必須確保整個分析期間整個執行系統的一致性,就好像整個執行系統看起來像被凍結在某個時間點上一樣,為了保證實現,必須進行GC停頓,停止所有java執行緒,(stop the world).

停頓後不需要一個不落的檢測,在類載入完成時,hotpot把儲存物件內什麼偏移量上有什麼型別資料計算出來,然後在特定位置(後文提到的安全點)記錄(使用OopMap資料結構)下棧和暫存器中哪些位置是引用,所以hotpot可以知道物件的引用資訊

安全點

hotpot如果為每一條指令都設定OopMap,GC成本會很高,所以GC停頓實際只有在安全點才會執行。一般選定為“是否具有讓程式長時間執行的特徵”為標準,一般是方法呼叫,迴圈跳轉,異常跳轉這些情況。所以一般設定在這些地方。另一個問題是如何讓所有執行緒都跑到最近的安全點位置再停下來

搶先試中斷

GC發生時,首先把所有執行緒都中斷,然後讓不在安全點位置中斷的執行緒恢復,跑到安全點上,然後再中斷。

主動式中斷

當GC需要主動式中斷執行緒的時候,不直接對執行緒操作,而是在安全點位置設定一個標誌,讓各個執行緒執行時主動去輪詢這個標誌,發現中斷標誌位真時就自己中斷掛起

安全區域

有些時候只有安全點是不夠的。為什麼不夠呢? 因為我們忘掉了一種常見執行的情況。我們忘了它,因為它實際上不是長時間執行,而是長時間閒置。有這樣一類情況程式不能及時響應GC觸發事件,比如sleep,因系統呼叫阻塞。 這些操作不是JVM能夠控制的。JVM在此期間不能夠響應GC事件。 因此,我們引入了安全區域的概念來解決這個問題。安全區域是其中引用不會改變的一段程式碼片段,那麼在其中任一點進行根列舉(GC)都是安全的。 換句話說,安全區域是安全點的一個很大的擴充套件。

線上程執行Safe Region中程式碼時,首先標識自己已經進入Safe Region,那麼,當在這段時間裡JVM要發起GC時,在安全區域裡的就自動被GC,它不用關心該區域裡的執行緒。

垃圾回收演算法

記憶體分割槽與回收演算法使用

依據垃圾回收機制作用的記憶體分割槽

垃圾回收區域主要分為:Permanent Space 和 Heap Space。

  1. Permanent 即 持久代(Permanent Generation),主要存放的是Java類定義資訊,與垃圾收集器要收集的Java物件關係不大。這個在執行時記憶體中其實就是執行時記憶體分割槽裡的方法區,定義為(非堆)。

  2. Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年輕代(Young Generation)。年老代和年輕代的劃分對垃圾收集影響比較大。

分代收集演算法

對於Heap裡的不同區域通常使用不同的演算法區解決。統稱為分代收集演算法:根據物件存活週期的不同將記憶體劃分為幾塊。一般是把Java堆分為新生代(年輕代)和老年代(年老代),這樣就可以根據各個年代的特點採用最適當的收集演算法。在新生代中,每次垃圾收集時都發現有大批物件死去(朝生夕死),只有少量存活,那就選用複製演算法,只需要付出少量存活物件的複製成本就可以完成收集。而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記—清理”或者“標記—整理”演算法來進行回收

標記清理演算法

可以使用在年老代,但不怎麼推薦。

演算法原理

首先標記出所有需要回收的物件,在標記完成後統一回收所有被標記的物件。

這裡寫圖片描述

缺點

1,效率問題,標記和清除兩個過程的效率都不高;
2,空間問題,標記清除之後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作

複製演算法

複製演算法的改進演算法用於年輕代的垃圾回收。

原理

將記憶體分成兩塊,每次只使用其中一塊,垃圾回收時,將標記的物件拷貝到另外一塊中,然後完全清除原來使用的那塊記憶體。複製後的空間是連續的。因為垃圾物件多於存活物件,複製演算法更高效
這裡寫圖片描述

缺點

雖然高效,但是這種演算法的代價是將記憶體縮小為了原來的一半,大大減小了可使用記憶體。

改進複製演算法(年輕代GC演算法)

Minor GC觸發條件:當Eden區滿(沒有足夠空間再進行分配)時,觸發Minor GC(記住GC是一個過程)。

原理

JVM把年輕代分為了三部分:1個Eden區和2個Survivor區(分別叫from和to)。預設比例為8:1。一般情況下,新建立的物件都會被分配到Eden區(一些大物件特殊處理),這些物件經過它自己的第一次Minor GC後,如果仍然存活,並且能被Survivor區容納,則將會被移到Survivor(to)區,並且將年齡設定為1。物件在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度(15)時,就會被移動到年老代中
這裡寫圖片描述
在GC開始的時候,物件只會存在於Eden區和名為“From”的Survivor區,Survivor區“To”是空的。緊接著進行GC,Eden區中所有存活的物件都會被複制到“To”而在“From”區中,仍存活的物件會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設定)的物件會被移動到年老代中,沒有達到閾值的物件會被複制到“To”區域。經過這次GC後,Eden區和From區已經被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到“To”區被填滿,“To”區被填滿之後,會將無法容納的新的存活物件移動到年老代中。
這裡寫圖片描述

總結來說:當Eden中無法再分配空間的時候,觸發Minor GC,將存活的物件放到Survivor區,之後的每次Minor GC都會既清理Eden,也清理From,將存活的複製到TO

關於年輕代的Jvm引數

1)-XX:NewSize和-XX:MaxNewSize
用於設定年輕代的大小,建議設為整個堆大小的1/3或者1/4,兩個值設為一樣大。

2)-XX:SurvivorRatio
用於設定Eden和其中一個Survivor的比值,這個值也比較重要。

3)-XX:+PrintTenuringDistribution
這個引數用於顯示每次Minor GC時Survivor區中各個年齡段的物件的大小

4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
用於設定晉升到老年代的物件年齡的最小值和最大值,每個物件在堅持過一次Minor GC之後,年齡就加1。

標記整理演算法(年老代GC演算法)

年老代通常使用的演算法,發生在該代的GC稱之為Full GC

原理

標記階段與標記清除演算法一樣。但後續並不是直接對可回收的物件進行清理,而是讓所有存活物件都想一端移動,然後清理。優點是不會造成記憶體碎片。
這裡寫圖片描述

Minor GC的觸發時機

Full GC的觸發時機

一般Full GC總會伴隨一次Minor GC
(1)呼叫System.gc時,系統建議執行Full GC,但是不必然執行

(2)老年代空間不足

(3)方法區(持久代)空間不足

(4)由Eden區、From Space區向To Space區複製時,物件大小大於To Space可用記憶體,則把該物件轉存到老年代,且老年代的可用記憶體小於該物件大小(Minor GC不安全

(5)通過Minor GC後進入老年代的平均大小大於老年代的可用記憶體(不冒險直接Full GC

垃圾收集器

這裡寫圖片描述

這裡寫圖片描述

年輕代垃圾收集器

三種年輕代的垃圾回收器:序列GC(Serial)、併發GC(Parallel Scavenge)和並行GC(ParNew)。全部使用複製演算法

Serial(序列GC)

原理

是一種單執行緒垃圾回收機制,而且不僅如此,它最大的特點就是在進行垃圾回收的時候,需要將所有正在執行的執行緒暫停(Stop The World)
這裡寫圖片描述

應用場景

只要我們能夠做到將它所停頓的時間控制在N個毫秒範圍內,是完全可以接受的,該收集器適用於單CPU、新生代空間較小及對暫停時間要求不是非常高的應用上,是client(客戶端)級別預設的GC方式,可以通過-XX:+UseSerialGC來強制指定。

ParNew(並行GC)

雖說同時做好幾件事,但這幾件事都是關於垃圾回收的。

原理

基本和Serial GC一樣,但本質區別是加入了多執行緒機制,提高了效率。
這裡寫圖片描述
可以使用-XX:ParallelGCThreads引數來限制垃圾收集的執行緒數。

使用場景

它可以被用在伺服器端(Server)上,同時它可以與CMS GC配合,所以,更加有理由將它置於Server端。

Parallel Scavenge(並行GC)

原理

吞吐量:就是CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值,即吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間),虛擬機器總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。該收集器是吞吐量優先收集器,也就是說優先吞吐量,可能會導致使用者感覺停頓時間長

使用場景

多核可以最大可能利用cpu,要求時間間隔短,主要適用於在後臺執行且不需要太多互動的任務,常應用在伺服器端。

年老代垃圾收集器

年老代垃圾收集器有以下三個

Serial Old(序列GC):

使用標記整理演算法,是Serial在年老代裡的版本。
1, 這個收集器的主要意義也是在於給Client模式下的虛擬機器使用。
2,如果在Server模式下,那麼它主要還有兩大用途:

  1. 一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用
  2. 另一種用途就是作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用

Parallel Old(並行GC)

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多執行緒和“標記-整理”演算法
這裡寫圖片描述
這樣可以用全套的吞吐量優先

CMS(併發GC)

基於“標記—清除”演算法
初始標記(CMS initial mark)、併發標記(CMS concurrenr mark)、重新標記(CMS remark)、併發清除(CMS concurrent sweep)。
這裡寫圖片描述
其中初始標記、重新標記這兩個步驟任然需要停頓其他使用者執行緒初始標記僅僅只是標記出GC ROOTS能直接關聯到的物件,速度很快,併發標記階段是進行GC ROOTS 根搜尋演算法階段,會判定物件是否存活。而重新標記階段則是為了修正併發標記期間,因使用者程式繼續執行而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。由於整個過程中耗時最長的併發標記和併發清除過程中,收集器執行緒都可以與使用者執行緒一起工作,所以整體來說,CMS收集器的記憶體回收過程是與使用者執行緒一起併發執行的

優點與缺點

優點是:高併發、高響應
缺點是:以下三點
1,CMS收集器對CPU資源非常敏感,隨著cpu數量增加效能增強。

2,CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure“,失敗後而導致另一次Full GC的產生也是由於在垃圾收集階段使用者執行緒還需要執行,即需要預留足夠的記憶體空間給使用者執行緒使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分記憶體空間提供併發收集時的程式運作使用,要是CMS執行期間預留的記憶體無法滿足程式其他執行緒需要,就會出現“Concurrent Mode Failure”失敗,這時候虛擬機器將啟動後備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。

3,最後一個缺點,CMS是基於“標記-清除”演算法實現的收集器,使用“標記-清除”演算法收集後,會產生大量碎片。空間碎片太多時,將會給物件分配帶來很多麻煩,比如說大物件,記憶體空間找不到連續的空間來分配不得不提前觸發一次Full GC

年輕代與年老代的組合

這裡寫圖片描述

年輕代與年老代通用(G1)

實現方式

這裡寫圖片描述
JDK1.7u4,是面向服務端應用的垃圾收集器。以期未來可以替換CMS,新生代和老年代都可以進行。
特點:
並行與併發:充分利用多CPU、多核環境下的硬體優勢,多CPU來縮短停頓時間。
分代收集:採用不同方式去處理新建物件以及熬過多次GC的舊物件以獲得更好的收集效果;
空間整合:整體上是標記整理,區域性是複製;有利於程式長時間執行,分配大物件時不會因為無法找到連續記憶體空間而提前觸發下一次GC。將Java堆分為多個大小相等的獨立區域,新生代和老年代不再是物理隔離的。
可預測的停頓:除了追求低停頓,還能建立可預測的停頓時間模型。
運作步驟:初始標記、併發標記、最終標記、篩選回收。
最終標記需要停頓執行緒,但是可以並行執行。

優勢

相比CMS收集器有不少改進,

  • 可以併發

  • 首先基於標記-整理演算法,不會產生記憶體碎片問題

  • 其次,可以比較精確的控制停頓
  • -

記憶體分配與回收策略

記憶體分配在不同的垃圾收集器裡是不同的,這裡介紹幾條通用規則幾條規則

物件優先在Eden分配

通過-Xms20M、-Xmx20M、-Xmn10M這3個引數限制了Java堆大小為20MB,不可擴充套件,其中10MB分配給新生代,剩下的10MB分配給老年代。-XX:SurvivorRatio=8決定了新生代中Eden區與一個Survivor區的空間比例是8:1。Eden大小為8M。向Eden中加四個物件:allocation1(2M)、allocation(2M)、allocation(2M),當要加入allocation(4M)時發現Eden已經被佔用了6MB,剩餘空間已不足以分配allocation4所需的4MB記憶體,因此發生Minor GC。但三個物件都是存活的,GC期間虛擬機器又發現已有的3個2MB大小的物件全部無法放入Survivor空間(Survivor空間只有1MB大小),所以只好通過分配擔保機制提前轉移到老年代去。然後allocation(4M)進入Minor GC

大物件直接進入老年代

虛擬機器提供了一個-XX:PretenureSizeThreshold引數,令大於這個設定值的物件直接在老年代分配。這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的記憶體複製

長期存活的物件將進入老年代

如果物件在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且物件年齡設為1。物件在Survivor區中每“熬過”一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(預設為15歲),就將會被晉升到老年代中。物件晉升老年代的年齡閾值,可以通過引數-XX:MaxTenuringThreshold設定

動態物件年齡判斷

為了能更好地適應不同程式的記憶體狀況,虛擬機器並不是永遠地要求物件的年齡必須達到
了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡.例如Survivor空間10個物件,有6個3歲,1個4歲,3個2歲,則4歲的進入老年代空間。

空間分配擔保

例子:例如Eden有8M物件,from有1M物件,如果Minor GC後還活著的物件大於to(1M)的容量,則一定需要老年代的空間擔保分配。

空間分配擔保策略

First,虛擬機器會先檢查老年代最大可用的連續空間是否大於新生代所有物件總空間,如果這個條件成立,那麼Minor GC可以確保是安全的。比如說老年代有10M,就算年輕代的物件在Minor GC後都活著也可以足夠容納(加起來才9M)如果不成立,則虛擬機器會檢視HandlePromotionFailure設定值是否允許擔保失敗

First—1 如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代物件的平均大小

  • First—1—-1如果大於,將嘗試著進行一次Minor GC,儘管這次Minor GC是有風險的,這裡的風險指的是:如果某次Minor GC存活後的物件突增,遠遠高於平均值的話,依然會導致擔保失敗(Handle Promotion Failure)。如果出現了HandlePromotionFailure失敗,那就只好在失敗後重新發起一次Full GC
  • First—1—-2如果小於那麼這時要進行一次Full GC。

First—2 如果不允許,那麼這時要進行一次Full GC。

雖然擔保失敗時繞的圈子是最大的,但大部分情況下還是會將HandlePromotionFailure開關開啟,避免Full GC過於頻繁

記憶體分配基本流程

1,JVM會試圖為相關Java物件在年輕代的Eden區中初始化一塊記憶體區域。

  1. 如果放入的是大物件,則可以根據引數設定直接放入年老代

2, 當Eden區空間足夠時,記憶體申請結束。否則執行下一步。
3, Eden區空間無法再為新進物件分配記憶體的時候執行Minor GC,每次Minor GC之前都會執行一次空間分配擔保檢查策略。

3.1 ,FIRST====如果現有存活物件總大小大於Survivor區,則將存活物件放入年老代
3.2, Second====如果小於則將存活物件放到from中,並且設定年齡為1,然後from和to輪流交換角色,不停地Minor GC,每Minor GC一次,原來活著的年齡加1

  1. 如果From中年齡到一定值的(15)或者動態年齡分配方式滿足跳進則進入年老代
  2. 沒達到的進入to

4,直到to空間滿了無法容納,則將Minor GC後存活物件放入年老代。
5,當年老代空間不夠時,JVM會在年老代進行完全的垃圾回收(Full GC)。
6,Full GC後,若Survivor區及年老代仍然無法存放從Eden區複製過來的物件,則會導致JVM無法在Eden區為新生成的物件申請記憶體,即出現“Out of Memory”。

引數說明

引數說明

-Xmx3550m:設定JVM最大堆記憶體為3550M。

-Xms3550m:設定JVM初始堆記憶體為3550M。此值可以設定與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配記憶體。

-Xmn2g:設定年輕代大小為2G。在整個堆記憶體大小確定的情況下,增大年輕代將會減小年老代,反之亦然。此值關係到JVM垃圾回收,對系統性能影響較大,官方推薦配置為整個堆大小的3/8。

-XX:NewSize=1024m:設定年輕代初始值為1024M。

-XX:MaxNewSize=1024m:設定年輕代最大值為1024M。

-XX:PermSize=256m:設定持久代初始值為256M。

-XX:MaxPermSize=256m:設定持久代最大值為256M。

-XX:NewRatio=4:設定年輕代(包括1個Eden和2個Survivor區)與年老代的比值。表示年輕代比年老代為1:4。

-XX:SurvivorRatio=4:設定年輕代中Eden區與Survivor區的比值。表示2個Survivor區(JVM堆記憶體年輕代中預設有2個大小相等的Survivor區)與1個Eden區的比值為2:4,即1個Survivor區佔整個年輕代大小的1/6。

-XX:MaxTenuringThreshold=7:表示一個物件如果在Survivor區(救助空間)移動了7次還沒有被垃圾回收就進入年老代。如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代,對於需要大量常駐記憶體的應用,這樣做可以提高效率。如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件在年輕代存活時間,增加物件在年輕代被垃圾回收的概率,減少Full GC的頻率,這樣做可以在某種程度上提高服務穩定性。

相關推薦

JVM--13 【垃圾回收機制】 如何判斷物件是垃圾

一、物件已經死了嗎?           在堆裡面存放著Java世界中幾乎所有的物件例項,垃圾收集器在對堆進行回收前,第一件事情就是確定這些物件之中哪些是“存活”著,哪些已經“死去”(即不可能再被任何途徑使用的物件) 判斷物件是否存活的演算法:引用計數法、可達性分析演算法

jvm之java垃圾回收機制詳解

      傳統的C/C++等程式語言,需要程式設計師負責回收已經分配出去的記憶體。顯示進行垃圾回收是一件令人頭疼的事情,因為程式設計師並不總是知道記憶體應該何時進行釋放。如果一些分配出去的記憶體不能及時的回收就會引起系統執行速度下降,甚至導致程式癱瘓,這種現象稱為記憶體洩露

[深入理解JVM ]---Jvm垃圾回收機制

本篇部落格大部分內容來自《深入理解java虛擬機器》,也參考了http://jbutton.iteye.com/blog/1569746這篇文章的部分內容,這裡註明出處。這篇部落格也是這個系列的第二篇,在這篇部落格裡我會對java的記憶體回收機制做個詳細的整理。

深入理解JVM虛擬機器(二):垃圾回收機制

談起GC,應該是讓Java程式設計師最激動的一項技術,我相信每個Java程式設計師都有探究GC本質的衝動!JVM垃圾回收機制對於瞭解物件的建立和物件的回收極為重要,是每個Java程式設計師必須掌握的技能。 本部落格圍繞三個問題來展開 哪些記憶體需要回收? 什

深入理解JVM學習筆記(十九、JVM 垃圾回收機制---如何判斷物件是否為垃圾【引用計數法】)

一、引用計數法         引用計數演算法作為垃圾收集器最早的演算法,有其優勢,也有其劣勢,雖然現在的JVM都不再採用引用計數演算法進行垃圾回收【例如Sun的Java hotspot採用了火車演算法進行垃圾回收】,但這種演算法也並未被淘汰,在著名的單程序高併發快取Red

深入理解JVM學習筆記(二十二、JVM 垃圾回收機制---如何回收垃圾---回收策略【複製演算法】)

        上一節我們講到了標記-清除演算法因為需要進行兩次記憶體掃描導致效率不高,那麼這一節我們介紹一種複製演算法,比較好的解決了這個問題。        講複製演算法前,我們先回顧一下JVM的記憶體結構。JVM記憶體大體分為兩大塊,分別為執行緒共享區、執行緒獨佔區。

深入理解Java虛擬機器——JVM垃圾回收機制垃圾收集器詳解

一:概述 說起垃圾回收(Garbage Collection,GC),很多人就會自然而然地把它和Java聯絡起來。在Java中,程式設計師不需要去關心記憶體動態分配和垃圾回收的問題,顧名思義,垃圾回收就是釋放垃圾佔用的空間,這一切都交給了JVM來處理。本文主要解答三個

深入理解JVM()JVM類載入機制

7.1JVM類載入機制   虛擬機器把資料從Class檔案載入到記憶體,並且校驗、轉換解析和初始化最終形成可以被虛擬機器使用的Java型別,這就是虛擬機器的類載入機制。 7.2類載入的時機   1.類載入的步驟開始的順序: 載入(Loading) -> 驗證(Veri

深入理解JVM()JVM類加載機制

同步 擴展 父類 cin ssl 都是 mage java類型 java類 7.1JVM類加載機制   虛擬機把數據從Class文件加載到內存,並且校驗、轉換解析和初始化最終形成可以被虛擬機使用的Java類型,這就是虛擬機的類加載機制。 7.2類加載的時機   1.類加載的

深入JVM垃圾回收機制,值得你收藏

JVM可以說是為了Java開發人員遮蔽了很多複雜性,讓Java開發的變的更加簡單,讓開發人員更加關注業務而不必關心底層技術細節,這些複雜性包括記憶體管理,垃圾回收,跨平臺等,今天我們主要看看JVM的垃圾回收機制是怎麼執行的,希望能夠幫到大家, 哪些物件是垃圾呢? Java程式執行過程中時刻都在產生很多物件,我

JVM內存管理機制垃圾回收機制

mark 代碼 本地方法棧 final類 boot 存取 帶來 logs byte JVM自身結構物理圖: Java代碼編譯和執行的整個過程包含了以下三個重要的機制: 1.java源碼編譯機制 1)分析和輸入到符號表 class文件結構包含: 結構

深入理解 Java 垃圾回收機制

nbsp 循環引用 方式 不同的 整理 一個 復制 垃圾回收機制 提高 垃圾回收機制中的算法: 1.引用計數法:無法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能為0. 2 標記-清除算法:采用從根集合進行掃描,對存活

9.垃圾回收機制JVM

都在 可能 sys 機制 防止 情況 關閉 bject 泄露 9.垃圾回收機制和JVM 1.GC(Garbage Coolection)指垃圾回收機制。沒有提供相關api,手動回收,所有的內存分配和回收權限都在jvm中 2.System.gc():呼叫java虛擬機的垃

JVM垃圾回收機制

root 不一定 引用 不可達 tenured jvm 空間 大量 jvm垃圾回收 在java中是通過引用來和對象進行關聯的,也就是說如果要操作對象,必須通過引用來進行。那麽很顯然一個簡單的辦法就是通過引用計數來判斷一個對象是否可以被回收。不失一般性,如果一個對象沒有任何引

詳解JVM內存管理與垃圾回收機制 (上)

JVM 內存結構Java應用程序是運行在JVM上的,得益於JVM的內存管理和垃圾收集機制,開發人員的效率得到了顯著提升,也不容易出現內存溢出和泄漏問題。但正是因為開發人員把內存的控制權交給了JVM,一旦出現內存方面的問題,如果不了解JVM的工作原理,將很難排查錯誤。本文將從理論角度介紹虛擬機的內存管理和垃圾回

JVM垃圾回收機制 總結(垃圾收集、回收算法、垃圾回收器)

策略 .html clas 高並發 hotspot 指定 %20 引用關系 新增  相信和小編一樣的程序猿們在日常工作或面試當中經常會遇到JVM的垃圾回收問題,有沒有在夜深人靜的時候詳細捋一捋JVM垃圾回收機制中的知識點呢?沒時間捋也沒關系,因為小編接下來會給你捋

JVM垃圾回收機制原理

add IE 安全性 mod 銷毀 初始 文件 1.2 com JVM Java 虛擬機 Java 虛擬機(Java virtual machine,JVM)是運行 Java 程序必不可少的機制。JVM實現了Java語言最重要的特征:即平臺無關性。原理:編譯後的 Java

JVM原理(Java代碼編譯和執行的整個過程+JVM內存管理及垃圾回收機制

變化 並行 colspan 同時 簡單的 table 目前 動態 中心 轉載註明出處: http://blog.csdn.net/cutesource/article/details/5904501 JVM工作原理和特點主要是指操作系統裝入JVM是通過jdk中Java.ex

JVM垃圾回收機制演算法分析

JVM記憶體執行時資料區 一、什麼是垃圾回收機制gc垃圾回收機制&&演算法 什麼是垃圾回收機制: 不定時去堆記憶體清理不可達物件。不可達的物件並不會馬上就會直接回收,而是至少要經過兩次標記的過程。 public class Test { public st

JVM 垃圾回收機制與GC效能調優

一、GC概要: JVM堆相關知識     為什麼先說JVM堆?     JVM的堆是Java物件的活動空間,程式中的類的物件從中分配空間,其儲存著正在執行著的應用程式用到的所有物件。這些物件的建立方式就是那些new一類的操作,當物件