【面試必備】小夥伴栽在了JVM的記憶體分配策略。。。

發表於2019-09-27

週末有小夥伴留言說上週面試時被問到記憶體分配策略的問題,但回答的不夠理想,小夥伴說之前公號裡看過這一塊的文章的,當時看時很清楚,也知道各個策略是幹嘛的,但面試時腦子裡清楚,心裡很明白,但嘴裡就是說不清楚,說出來的就是像雲像霧又像風,最後面試官說他應該是不清楚這一塊的內容

這裡給小夥伴要再次說明下,任何知識點,先抓主幹,再摸細節。對於面試來說,能把各個主幹捋清楚,只要面試官要求不是太高,都是能過關的。畢竟jvm引數那麼多,難不成面試官揪著各個引數的作用不放?如果真遇到這種太過揪細節的,只能說江湖路遠,有緣再見!

物件的記憶體分配,往大方向上講,就是在上分配(但也可能經過JIT編譯後被拆散為標量型別並間接地棧上分配),物件主要分配在新生代的Eden區上,如果啟動了本地執行緒分配緩衝,將按執行緒優先在TLAB上分配。少數情況下可能會直接分配在老年代中。

物件優先在Eden分配

大多數情況下,物件在新生代Eden區中分配。當Eden區沒有足夠空間進行分配時,虛擬機器將發起一次Minor GC(前面篇章中有介紹過Minor GC)。但也有一種情況,在記憶體擔保機制下,無法安置的物件會直接進到老年代。

大物件直接進入老年代

大物件時指需要大量連續記憶體空間的Java物件,最典型的大物件就是那種很長的字串以及陣列。

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

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

虛擬機器給每個物件定義了一個物件年齡(Age)計數器。如果物件在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且物件年齡設為1 。物件在Survivor區中沒經過一次Minor GC,年齡就加1歲,當年齡達到15歲(預設值),就會被晉升到老年代中。

物件晉升老年代的年齡閾值,可以通過引數-XX:MaxTenuringThreshold設定。

接下來我們來回答JVM的分代年齡為什麼是15?而不是16,20之類的呢?

真的不是為什麼不能是其它數(除了15),著實是臣妾做不到啊!

事情是這樣的,HotSpot虛擬機器的物件頭其中一部分用於儲存物件自身的執行時資料,如雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等,這部分資料的長度在32位和64位的虛擬機器(未開啟壓縮指標)中分別為32bit和64bit,官方稱它為“Mark word”。

例如,在32位的HotSpot虛擬機器中,如果物件處於未被鎖定的狀態下,那麼Mark Word的32bit空間中25bit用於儲存物件雜湊碼,4bit用於儲存物件分代年齡,2bit用於儲存鎖標誌位,1bit固定為0 。

明白是什麼原因了嗎?物件的分代年齡佔4位,也就是0000,最大值為1111也就是最大為15,而不可能為16,20之類的了。

動態物件年齡判定

為了能更好的適應不同程式的記憶體狀況,虛擬機器並不是永遠地要求兌現過的年齡必須達到了MaxTenuringThreshold才能晉升老年代。

滿足如下條件之一,物件能晉升老年代:

  1. 物件的年齡達到了MaxTenuringThreshold(預設15)能晉升老年代。
  2. 如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

很多文章都只是注意到了上面描述的情況(包括阿里中介軟體公眾號發的一篇文章裡也只是這麼簡單的介紹),但如果只是這麼認識的話,會發現在實際的記憶體回收中有悖於此條規定。

舉個小栗子,如物件年齡5的佔34%,年齡6的佔36%,年齡7的佔30%,按那兩個標準,物件是不能進入老年代的,但Survivor都已經100%了啊?

大家可以關注這個引數TargetSurvivorRatio,目標存活率,預設為50%。大致意思就是說年齡從小到大累加,如加入某個年齡段(如栗子中的年齡6)後,總佔用超過Survivor空間TargetSurvivorRatio的時候,從該年齡段開始及大於的年齡物件就要進入老年代(即栗子中的年齡6,7物件)。動態物件年齡判斷,主要是被TargetSurvivorRatio這個引數來控制。而且算的是年齡從小到大的累加和,而不是某個年齡段物件的大小。

空間分配擔保

在發生Minor GC之前,虛擬機器會先檢查老年代最大可用的連續空間是否大於新生代所有物件總空間,如果這個條件成立,那麼Minor GC可以確保是安全的。如果不成立,則虛擬機器會檢視HandlePromotionFailure設定值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代物件的平均大小,如果大於,將嘗試著進行一次Minor GC,儘管這次Minor GC是有風險的;如果小於,或者HandlePromotionFailure設定不允許冒險,那這時也要改為進行一次Full GC 。

上面說的風險是什麼呢?我們知道,新生代使用複製收集演算法,但為了記憶體利用率,只使用其中一個Survivor空間來作為輪換備份,因此當出現大量物件在Minor GC後仍然存活的情況(最極端的情況就是記憶體回收後新生代中所有物件都存活),就需要老年代進行分配擔保,把Survivor無法容納的物件直接進入老年代。

總結腦圖
file

備註: 腦圖太大,如需高清完整大圖,可在“猿人谷”公眾號後臺輸入:jvm

相關文章