一次年輕代GC長暫停問題的解決與思考

年輕代晉升機制

為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機并不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代嘹履,無須等到MaxTenuringThreshold中要求的年齡
《深入理解Java虛擬機》一書中提到该肴,對象晉升年齡的閾值是動態(tài)判定的。

不過經(jīng)查閱其他資料和驗證后堪藐,發(fā)現(xiàn)此處和《深入理解Java虛擬機》解釋的有些出入(或者是書上解釋的不夠清楚)

其實就是按年齡給對象分組,取total(累加值挑围,小于等與當(dāng)前年齡的對象總大薪妇骸)最大的年齡分組,如果該分組的total大于survivor的一半杉辙,就將晉升年齡閾值更新為該分組的年齡

注意:不是是超過survivor一半就晉升模捂,超過survivor一半只會重新設(shè)置晉升閾值(threshold),在下一次GC才會使用該新閾值

3544342K->374555K(3774912K), 0.1444710 secs] 年輕代

3544342K->374555K(10066368K), 0.1446290 secs] 全堆

從上面第一次的GC日志也可以證明這個結(jié)論,在這次GC中全堆的內(nèi)存變化和年輕代內(nèi)存變化是相等的狂男,所以并沒有發(fā)生對象的晉升

就像上面的日志中综看,第一次GC只是將threshold設(shè)置為1,因為此時survivor一半為214728704 bytes岖食,而年齡為1的對象總和有315529928 bytes寓搬,超過了Desired survivor size,所以在本次GC后將threshold設(shè)置為年齡為1的對象年齡1

這里更新了對象晉升年齡閾值為1

Desired survivor size 214728704 bytes, new threshold 1 (max 15)
- age   1:  315529928 bytes,  315529928 total
- age   2:   40956656 bytes,  356486584 total
- age   3:    8408040 bytes,  364894624 total

這里順便解釋下這個年齡分布的輸出內(nèi)容:

- age 1: 315529928 bytes, 315529928 total

  • age 1表示年齡為1的對象分組县耽,315529928 bytes表示年齡為1的對象占用內(nèi)存大小

315529928 total這個是一個累加值句喷,表示小于等于當(dāng)前分組年齡的對象總大小。先把對象按年齡分組兔毙,age 1的分組total為age 1總大型偾怼(前面的xxx bytes),age 2的分組total為age 1 + age 2總大小澎剥,age n的分組total為age 1 + age 2 + ... +age n的總大小锡溯,累加規(guī)則如下圖所示

image.png

當(dāng)total最大的分組的total值超過了survivor/2時,就會更新晉升閾值

在第二次年輕代GC“長暫停年輕代GC日志”中哑姚,由于新的晉升年齡閾值為1祭饭,所以那些經(jīng)歷了一次GC并存活并且現(xiàn)在仍然可達(reachable)的對象們就會發(fā)生晉升了

由于此次GC發(fā)生了363M的對象晉升,所以導(dǎo)致了長暫停

思考

JVM中這個“動態(tài)對象年齡判定”真的是合理的嗎叙量?個人認(rèn)為機制是好的倡蝙,可以更好的適應(yīng)不同程序的內(nèi)存狀況,但不是任何場景都適合绞佩,比如在本文中這個剛啟動不就GC的場景下就會有問題

因為在程序剛啟動時寺鸥,大多數(shù)對象年齡都是0或者1,很容易出現(xiàn)年齡為1的大量存活對象品山;在這個“動態(tài)對象年齡判定”機制下胆建,就會導(dǎo)致新的晉升閾值被設(shè)置為1,導(dǎo)致這些不該晉升的對象發(fā)生了晉升

比如程序在初始化肘交,正在加載各種資源時發(fā)生了Young GC笆载,加載邏輯還在執(zhí)行中,很多新建的對象年齡在這次GC時還是可達的(reachable)

經(jīng)歷了這次GC后涯呻,這些對象年齡更新為1凉驻,但是由于“動態(tài)對象年齡判定”機制的影響,晉升年齡閾值更新為了“最大的對象年齡分組”的年齡魄懂,也就是這批剛經(jīng)歷了一次GC的對象們

在這次GC之后不久沿侈,資源初始化完成了,涉及的相關(guān)對象有很可能不可達了市栗,但是由于剛才晉升年齡閾值被更新為了1,在下一次正常的Young GC這批年齡為1的對象會直接發(fā)生晉升,提前或者說錯誤的發(fā)生了晉升

解決方案

經(jīng)查閱文檔填帽、資料蛛淋,發(fā)現(xiàn)“動態(tài)年齡判定”這個機制并不能禁用,所以如果想解決這個問題篡腌,只有靠“繞過”這個計算規(guī)則了

動態(tài)年齡的判定褐荷,是根據(jù)Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半來判定的,那么根據(jù)這個機制解決也很簡單

由于我們足夠了解自己的系統(tǒng)嘹悼,清楚的知道加載資源所需的大概內(nèi)存叛甫,完全可以設(shè)定一個大于這些暫時可達的對象總和的數(shù)值來作為survivor的容量

比如上面的日志中,第一次GC后年齡為1的對象有315529928 Bytes(300M)杨伙,Desired survivor size為(survivor size /2)214728704 bytes(204M)其监,那么survivor就可以設(shè)置為600M以上。

不過為了穩(wěn)妥限匣,還是將survivor調(diào)到800M抖苦,這樣desired survivor size就是400M左右,在第一次Young GC后米死,就不會因年齡為1的對象總和超過了desired survivor size而導(dǎo)致晉升年齡閾值的更新了锌历,從而也就不會有提前/錯誤晉升而導(dǎo)致的GC長暫停問題

survivor不可以直接指定大小,不過可以通過-XX:SurvivorRatio這種調(diào)節(jié)比例的方式來調(diào)節(jié)survivor大小

-XX:SurvivorRatio=8

表示兩個Survivor和Edgen區(qū)的比峦筒,8表示兩個Survivor:Eden=2:8究西,即一個Survivor占新生代的1/10。

計算方式為:

Survivor Size(1) = Young Generation Size / (2+SurvivorRatio)
Eden Size = Young Generation Size / (2+SurvivorRatio) * SurvivorRatio

擴展閱讀

為什么晉升300M比年輕代回收3G還要慢這么多倍
根據(jù)復(fù)制算法的特性物喷,復(fù)制算法的時間消耗主要取決于存活對象的大小怔揩,而不是總空間的大小

比如上面4G的年輕代(實際只有Eden+S0可用),GC時只需要從GC ROOTS開始遍歷對象圖脯丝,將可達的對象復(fù)制至S1即可商膊,并不需要遍歷整個年輕代

在上面那次長暫停GC日志中,發(fā)生了363M的晉升宠进,300M左右的回收晕拆,對比第一次GC基本可以得出,花費的1.5S基本上都是在晉升操作

那么為什么晉升操作這么耗時呢材蹬?

這里沒有深入研究Oracle JVM實現(xiàn)的年輕代晉升細節(jié)实幕,不過晉升涉及跨代復(fù)制(其實都年輕代和老年代都是heap,在復(fù)制這件事上本質(zhì)上沒什么區(qū)別堤器,都是memcpy而已昆庇,只是需要額外處理的邏輯更多了)
,所需處理的邏輯會更復(fù)雜一些闸溃,比如指針的更新等操作整吆,更耗時也是可以理解的拱撵,

本地代碼模擬

這里也附上一段可以在本地模擬問題的代碼,Oracle JDK7下可直接運行測試

//jdk7.表蝙。

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class PromotionTest {
    public static void main(String[] args) throws IOException {
        //模擬初始化資源場景
        List<Object> dataList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            dataList.add(new InnerObject());
        }
        //模擬流量進入場景
        for (int i = 0; i < 73; i++) {
            if(i == 72){
                System.out.println("Execute young gc...Adjust promotion threshold to 1");
            }
            new InnerObject();
        }
        System.out.println("Execute full gc...dataList has been promoted to cms old space");
        //這里注意dataList中的對象在這次Full GC后會進入老年代
        System.gc();
    }
    public static byte[] createData(){
        int dataSize = 1024*1024*4;//4m
        byte[] data = new byte[dataSize];
        for (int j = 0; j < dataSize; j++) {
            data[j] = 1;
        }
        return data;
    }
    static class InnerObject{
        private Object data;

        public InnerObject() {
            this.data = createData();
        }
    }
}

jvm options

-server -Xmn400M -XX:SurvivorRatio=9 -Xms1000M -Xmx1000M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintGCApplicationStoppedTime -XX:+UseConcMarkSweepGC

感謝大家對作者的支持拴测,如果覺得文章不錯,對大家有所幫助府蛇,大家可以幫作者點點關(guān)注+轉(zhuǎn)發(fā)集索。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汇跨,隨后出現(xiàn)的幾起案子务荆,更是在濱河造成了極大的恐慌,老刑警劉巖穷遂,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件函匕,死亡現(xiàn)場離奇詭異,居然都是意外死亡塞颁,警方通過查閱死者的電腦和手機浦箱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祠锣,“玉大人酷窥,你說我怎么就攤上這事“橥” “怎么了蓬推?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長澡腾。 經(jīng)常有香客問我沸伏,道長,這世上最難降的妖魔是什么动分? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任毅糟,我火速辦了婚禮,結(jié)果婚禮上澜公,老公的妹妹穿的比我還像新娘姆另。我一直安慰自己,他們只是感情好坟乾,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布迹辐。 她就那樣靜靜地躺著,像睡著了一般甚侣。 火紅的嫁衣襯著肌膚如雪明吩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天殷费,我揣著相機與錄音印荔,去河邊找鬼低葫。 笑死,一個胖子當(dāng)著我的面吹牛躏鱼,可吹牛的內(nèi)容都是我干的氮采。 我是一名探鬼主播殷绍,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼染苛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了主到?” 一聲冷哼從身側(cè)響起茶行,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎登钥,沒想到半個月后畔师,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡牧牢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年看锉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塔鳍。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡伯铣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轮纫,到底是詐尸還是另有隱情腔寡,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布掌唾,位于F島的核電站放前,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏糯彬。R本人自食惡果不足惜凭语,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撩扒。 院中可真熱鬧似扔,春花似錦、人聲如沸却舀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挽拔。三九已至辆脸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間螃诅,已是汗流浹背啡氢。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工状囱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倘是。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓亭枷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搀崭。 傳聞我的和親對象是個殘疾皇子叨粘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355