內存分配與回收策略——深入理解Java虛擬機

前言

Java技術提倡的是自動管理內存,可以簡單說成兩個問題:給對象分配內存和回收對象內存,這一部分對於看過前幾篇的讀者應該不陌生了,這兒我們再來談一談內存放分配。

內存分配與回收策略——深入理解Java虛擬機

正文

對象內存分配主要是在堆上,新創建的對象大多在新生代的Eden區上,除非啟用了本地線程分配緩衝,將會按線程優先在TLAB上分配,當然也有一些可能會直接分配在老年代。所以具體分配在哪兒不是固定的,主要取決於當前使用的是哪一種垃圾收集器組合。

1.對象優先在Eden分配

在大多數情況下,對象還是分配在Eden區,只有當Eden區已經滿以後或者新對象沒辦法放入的時候,虛擬機就會觸發一次Minor GC。

這裡先提一下Minor GC和Major GC/Full GC,防止後面講到可能會不熟悉。

新生代GC(Minor GC):指發生在新生代的垃圾收集行為,因為Java對象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。

老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Minor GC。 Major GC的速度一般會比Minor GC慢10倍以上。

2.大對象直接進入老年代

大對象,就是字面意思,創建時所需要的連續內存比較大,最常見的就是很長的字符串或者數組,大對象對於虛擬機來說並不是好事,經常出現那種“朝生夕滅”的短命對象,內存為了安置這些大傢伙,不得不提前觸發垃圾收集來獲取足夠的連續空間存放它們,這對於虛擬機來說並不是好事兒,所以在編碼時,程序員就要有意識的避開這些大對象。

虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配。 這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的內存複製。

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

虛擬機採用了分代收集的思想來管理內存,那麼內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。 如果是現在這個問題給到你,你會怎麼做呢?可以談一談,面試官經常會這麼問考察個人的思維能力。

在實際上,虛擬機採用的方法我們肯定很熟悉,虛擬機給每個對象定義了一個對象年齡(Age)計數器,還記得程序計數器?記得標記法回收垃圾?都是採用標記計數。 對象在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納的話,就被移動到Survivor空間中,並且對象年齡設為1。在以後垃圾回收以後,每次歷經一次,年齡就增加1,當它的年齡增加到一個閾值(默認為15歲),就將會被晉升到老年代中。

4.動態對象年齡判定

前面已經介紹過採用年齡計數的方法將對象晉升到上一層內存中,為了能更好地適應不同程序的內存狀況,虛擬機並不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中,存活了大量年齡相同的對象,一旦他們所佔的空間大於總內存空間的一半時,年齡大於或等於該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

5.空間分配擔保

在發生Minor GC之前,虛擬機會先檢查老年代可用的連續空間是否大於所有新生代的總空間,如果大於的話,那麼這個GC就可以保證安全,如果不成立的,那麼可能會造成晉升老年代的時候內存不足。在這樣的情況下,虛擬機會先檢查HandlePromotionFailure設置值是否允許擔保失敗,如果是允許的,那麼說明虛擬機允許這樣的風險存在並堅持運行,然後檢查老年代的最大連續可用空間是否大於歷次晉升老年代對象的平均大小,如果大於的話,就執行Minor GC,如果小於,或者HandlePromotionFailure設置不允許冒險,那麼就會先進行一次Full GC將老年代的內存清理出來,然後再判斷。

上面提到的風險,小瘋子在這裡在提醒一下,新生代因為存活對象少採用複製算法,但為了內存利用率,只使用其中的一個Survivor空間,將存活的對象備份到Survivor空間上,一旦出現大量對象在一次Minor GC以後依然存活(最壞的計劃就是沒有發現有對象死亡需要清理),那麼就需要老年代來分擔一部分內存,把在Survivor上分配不下的對象直接進入老年代,因為我們不知道實際上具體需要多大內存,我們只能估算一個合理值,這個值採用的方法就是計算出每次晉升老年代的平均內存大小作為參考,如果需要的話,那就提前進行一次Full GC.

取平均值在大多數情況下是可行的,但是因為內存分配的不確定性太多,保不定哪次運行突然出現某些大對象或者Minor GC以後多數對象依然存活,導致內存遠遠高於平均值的話,依然會導致擔保失敗(Handle Promotion Failure)。如果出現了HandlePromotionFailure失敗,那就只好在失敗後重新發起一次Full GC。這樣的情況下,擔保失敗是要付出代價的,饒了好一個圈子還是回來重新Full GC,是不是我們就必要採用這樣的方式呢?大部分情況下都還是會將HandlePromotionFailure開關打開,畢竟失敗的機率比較小,這樣的擔保可以避免Full GC過於頻繁,垃圾收集器頻繁的啟動肯定是不好的。

到今天內存管理,垃圾收集就結束啦,應對大部分面試應該夠了,當然還有幾個點,一個是虛擬機的安全策略,還有一個是虛擬機性能檢測與調優,安全策略是我在面試趨勢時,偶然機會,面對一家網絡安全的公司,恰恰我看過虛擬機的安全策略,因此提到安全大大加分,性能調優因為在現如今Java作為電商最火的語言,分佈式已經不再那麼神祕,對於高併發性能的服務器調優與設計是一定會問到的,清楚如何操作絕對滿滿的加分,到時候記得來感謝小瘋子哦!後面慢慢會提到的,喜歡記得關注哦!

下一節帶來一些調優的工具吧。

相關推薦

推薦中...