【面試必備】小夥伴栽在了JVM的內存分配策略。。。
- 2019 年 10 月 3 日
- 筆記
周末有小夥伴留言說上周面試時被問到內存分配策略的問題,但回答的不夠理想,小夥伴說之前公號里看過這一塊的文章的,當時看時很清楚,也知道各個策略是幹嘛的,但面試時腦子裡清楚,心裏很明白,但嘴裏就是說不清楚,說出來的就是像雲像霧又像風,最後面試官說他應該是不清楚這一塊的內容
這裡給小夥伴要再次說明下,任何知識點,先抓主幹,再摸細節。對於面試來說,能把各個主幹捋清楚,只要面試官要求不是太高,都是能過關的。畢竟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才能晉陞老年代。
滿足如下條件之一,對象能晉陞老年代:
- 對象的年齡達到了MaxTenuringThreshold(默認15)能晉陞老年代。
- 如果在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無法容納的對象直接進入老年代。
總結腦圖
備註: 腦圖太大,如需高清完整大圖,可在「猿人谷」公眾號後台輸入:jvm