【JVM故障問(wèn)題排查心得】「內(nèi)存診斷系列」JVM內(nèi)存與Kubernetes中pod的內(nèi)存、容器的內(nèi)存不一致所引發(fā)的OOMKilled問(wèn)題總結(jié)(下)

承接上文

之前文章根據(jù)《【JVM故障問(wèn)題排查心得】「內(nèi)存診斷系列」JVM內(nèi)存與Kubernetes中pod的內(nèi)存椭住、容器的內(nèi)存不一致所引發(fā)的OOMKilled問(wèn)題總結(jié)(上)》我們知道了如何進(jìn)行設(shè)置和控制對(duì)應(yīng)的堆內(nèi)存和容器內(nèi)存的之間的關(guān)系,所以防止JVM的堆內(nèi)存超過(guò)了容器內(nèi)存,導(dǎo)致容器出現(xiàn)OOMKilled的情況立轧。但是在整個(gè)JVM進(jìn)程體系而言,不僅僅只包含了Heap堆內(nèi)存躏吊,其實(shí)還有其他相關(guān)的內(nèi)存存儲(chǔ)空間是需要我們考慮的氛改,一邊防止這些內(nèi)存空間會(huì)造成我們的容器內(nèi)存溢出的場(chǎng)景,正如下圖所示比伏。


image

接下來(lái)了我們需要進(jìn)行分析出heap之外的一部分就是對(duì)外內(nèi)存就是Off Heap Space胜卤,也就是Direct buffer memory堆外內(nèi)存。主要通過(guò)的方式就是采用Unsafe方式進(jìn)行申請(qǐng)內(nèi)存赁项,大多數(shù)場(chǎng)景也會(huì)通過(guò)Direct ByteBuffer方式進(jìn)行獲取葛躏。好廢話不多說(shuō)進(jìn)入正題。

JVM參數(shù)MaxDirectMemorySize

我們先研究一下jvm的-XX:MaxDirectMemorySize悠菜,該參數(shù)指定了DirectByteBuffer能分配的空間的限額舰攒,如果沒有顯示指定這個(gè)參數(shù)啟動(dòng)jvm,默認(rèn)值是xmx對(duì)應(yīng)的值(低版本是減去幸存區(qū)的大谢诖住)摩窃。

DirectByteBuffer對(duì)象是一種典型的”冰山對(duì)象”,在堆中存在少量的泄露的對(duì)象芬骄,但其下面連接用堆外內(nèi)存猾愿,這種情況容易造成內(nèi)存的大量使用而得不到釋放

-XX:MaxDirectMemorySize

-XX:MaxDirectMemorySize=size 用于設(shè)置 New I/O (java.nio) direct-buffer allocations 的最大大小,size 的單位可以使用 k/K德玫、m/M匪蟀、g/G;如果沒有設(shè)置該參數(shù)則默認(rèn)值為 0宰僧,意味著JVM自己自動(dòng)給NIO direct-buffer allocations選擇最大大小材彪。

-XX:MaxDirectMemorySize的默認(rèn)值是什么?

在sun.misc.VM中琴儿,它是Runtime.getRuntime.maxMemory()段化,這就是使用-Xmx配置的內(nèi)容。而對(duì)應(yīng)的JVM參數(shù)如何傳遞給JVM底層的呢造成?主要通過(guò)的是hotspot/share/prims/jvm.cpp显熏。我們來(lái)看一下jvm.cpp的JVM源碼來(lái)分一下。

  // Convert the -XX:MaxDirectMemorySize= command line flag
  // to the sun.nio.MaxDirectMemorySize property.
  // Do this after setting user properties to prevent people
  // from setting the value with a -D option, as requested.
  // Leave empty if not supplied
  if (!FLAG_IS_DEFAULT(MaxDirectMemorySize)) {
    char as_chars[256];
    jio_snprintf(as_chars, sizeof(as_chars), JULONG_FORMAT, MaxDirectMemorySize);
    Handle key_str = java_lang_String::create_from_platform_dependent_str("sun.nio.MaxDirectMemorySize", CHECK_NULL);
    Handle value_str  = java_lang_String::create_from_platform_dependent_str(as_chars, CHECK_NULL);
    result_h->obj_at_put(ndx * 2,  key_str());
    result_h->obj_at_put(ndx * 2 + 1, value_str());
    ndx++;
  }

jvm.cpp 里頭有一段代碼用于把 - XX:MaxDirectMemorySize 命令參數(shù)轉(zhuǎn)換為 key 為 sun.nio.MaxDirectMemorySize的屬性晒屎。我們可以看出來(lái)他轉(zhuǎn)換為了該屬性之后喘蟆,進(jìn)行設(shè)置和初始化直接內(nèi)存的配置缓升。針對(duì)于直接內(nèi)存的核心類就在http://www.docjar.com/html/api/sun/misc/VM.java.html。大家有興趣可以看一下對(duì)應(yīng)的視線蕴轨。在JVM源碼里面的目錄是:java.base/jdk/internal/misc/VM.java港谊,我們看一下該類關(guān)于直接內(nèi)存的重點(diǎn)部分。

public class VM {

    // the init level when the VM is fully initialized
    private static final int JAVA_LANG_SYSTEM_INITED     = 1;
    private static final int MODULE_SYSTEM_INITED        = 2;
    private static final int SYSTEM_LOADER_INITIALIZING  = 3;
    private static final int SYSTEM_BOOTED               = 4;
    private static final int SYSTEM_SHUTDOWN             = 5;


    // 0, 1, 2, ...
    private static volatile int initLevel;
    private static final Object lock = new Object();

    //......

    // A user-settable upper limit on the maximum amount of allocatable direct
    // buffer memory.  This value may be changed during VM initialization if
    // "java" is launched with "-XX:MaxDirectMemorySize=<size>".
    //
    // The initial value of this field is arbitrary; during JRE initialization
    // it will be reset to the value specified on the command line, if any,
    // otherwise to Runtime.getRuntime().maxMemory().
    //
    private static long directMemory = 64 * 1024 * 1024;

上面可以看出來(lái)64MB最初是任意設(shè)置的橙弱。在-XX:MaxDirectMemorySize 是用來(lái)配置NIO direct memory上限用的VM參數(shù)歧寺。可以看一下JVM的這行代碼棘脐。

product(intx, MaxDirectMemorySize, -1,
        "Maximum total size of NIO direct-buffer allocations")

但如果不配置它的話斜筐,direct memory默認(rèn)最多能申請(qǐng)多少內(nèi)存呢?這個(gè)參數(shù)默認(rèn)值是-1蛀缝,顯然不是一個(gè)“有效值”顷链。所以真正的默認(rèn)值肯定是從別的地方來(lái)的。


    // Returns the maximum amount of allocatable direct buffer memory.
    // The directMemory variable is initialized during system initialization
    // in the saveAndRemoveProperties method.
    //
    public static long maxDirectMemory() {
        return directMemory;
    }

    //......

    // Save a private copy of the system properties and remove
    // the system properties that are not intended for public access.
    //
    // This method can only be invoked during system initialization.
    public static void saveProperties(Map<String, String> props) {
        if (initLevel() != 0)
            throw new IllegalStateException("Wrong init level");

        // only main thread is running at this time, so savedProps and
        // its content will be correctly published to threads started later
        if (savedProps == null) {
            savedProps = props;
        }

        // Set the maximum amount of direct memory.  This value is controlled
        // by the vm option -XX:MaxDirectMemorySize=<size>.
        // The maximum amount of allocatable direct buffer memory (in bytes)
        // from the system property sun.nio.MaxDirectMemorySize set by the VM.
        // If not set or set to -1, the max memory will be used
        // The system property will be removed.
        String s = props.get("sun.nio.MaxDirectMemorySize");
        if (s == null || s.isEmpty() || s.equals("-1")) {
            // -XX:MaxDirectMemorySize not given, take default
            directMemory = Runtime.getRuntime().maxMemory();
        } else {
            long l = Long.parseLong(s);
            if (l > -1)
                directMemory = l;
        }
        // Check if direct buffers should be page aligned
        s = props.get("sun.nio.PageAlignDirectMemory");
        if ("true".equals(s))
            pageAlignDirectMemory = true;
    }
    //......
}

從上面的源碼可以讀取 sun.nio.MaxDirectMemorySize 屬性内斯,如果為 null 或者是空或者是 - 1蕴潦,那么則設(shè)置為 Runtime.getRuntime ().maxMemory ();如果有設(shè)置 MaxDirectMemorySize 且值大于 - 1俘闯,那么使用該值作為 directMemory 的值潭苞;而 VM 的 maxDirectMemory 方法則返回的是 directMemory 的值。

因?yàn)楫?dāng)MaxDirectMemorySize參數(shù)沒被顯式設(shè)置時(shí)它的值就是-1真朗,在Java類庫(kù)初始化時(shí)maxDirectMemory()被java.lang.System的靜態(tài)構(gòu)造器調(diào)用此疹,走的路徑就是這條:

if (s.equals("-1")) {  
    // -XX:MaxDirectMemorySize not given, take default  
    directMemory = Runtime.getRuntime().maxMemory();  
}

而Runtime.maxMemory()在HotSpot VM里的實(shí)現(xiàn)是:

JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void))  
  JVMWrapper("JVM_MaxMemory");  
  size_t n = Universe::heap()->max_capacity();  
  return convert_size_t_to_jlong(n);  
JVM_END  

這個(gè)max_capacity()實(shí)際返回的是 -Xmx減去一個(gè)survivor space的預(yù)留大小。

結(jié)論分析說(shuō)明

MaxDirectMemorySize沒顯式配置的時(shí)候遮婶,NIO direct memory可申請(qǐng)的空間的上限就是-Xmx減去一個(gè)survivor space的預(yù)留大小蝗碎。例如如果您不配置-XX:MaxDirectMemorySize并配置-Xmx5g,則"默認(rèn)" MaxDirectMemorySize也將是5GB-survivor space區(qū)旗扑,并且應(yīng)用程序的總堆+直接內(nèi)存使用量可能會(huì)增長(zhǎng)到5 + 5 = 10 Gb 蹦骑。

其他獲取 maxDirectMemory 的值的API方法

BufferPoolMXBean 及 JavaNioAccess.BufferPool (通過(guò)SharedSecrets獲取) 的 getMemoryUsed 可以獲取 direct memory 的大小臀防;其中 java9 模塊化之后眠菇,SharedSecrets 從原來(lái)的 sun.misc.SharedSecrets 變更到 java.base 模塊下的 jdk.internal.access.SharedSecrets;要使用 --add-exports java.base/jdk.internal.access=ALL-UNNAMED 將其導(dǎo)出到 UNNAMED袱衷,這樣才可以運(yùn)行

public BufferPoolMXBean getDirectBufferPoolMBean(){
        return ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)
                .stream()
                .filter(e -> e.getName().equals("direct"))
                .findFirst()
                .orElseThrow();
}
public JavaNioAccess.BufferPool getNioBufferPool(){
     return SharedSecrets.getJavaNioAccess().getDirectBufferPool();
}

內(nèi)存分析問(wèn)題

-XX:+DisableExplicitGC 與 NIO的direct memory

  • 用了-XX:+DisableExplicitGC參數(shù)后捎废,System.gc()的調(diào)用就會(huì)變成一個(gè)空調(diào)用,完全不會(huì)觸發(fā)任何GC(但是“函數(shù)調(diào)用”本身的開銷還是存在的哦~)致燥。

  • 做ygc的時(shí)候會(huì)將新生代里的不可達(dá)的DirectByteBuffer對(duì)象及其堆外內(nèi)存回收了登疗,但是無(wú)法對(duì)old里的DirectByteBuffer對(duì)象及其堆外內(nèi)存進(jìn)行回收,這也是我們通常碰到的最大的問(wèn)題,如果有大量的DirectByteBuffer對(duì)象移到了old辐益,但是又一直沒有做cms gc或者full gc断傲,而只進(jìn)行ygc,那么我們的物理內(nèi)存可能被慢慢耗光智政,但是我們還不知道發(fā)生了什么艳悔,因?yàn)閔eap明明剩余的內(nèi)存還很多(前提是我們禁用了System.gc)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末女仰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子抡锈,更是在濱河造成了極大的恐慌疾忍,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件床三,死亡現(xiàn)場(chǎng)離奇詭異一罩,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)撇簿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門聂渊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人四瘫,你說(shuō)我怎么就攤上這事汉嗽。” “怎么了找蜜?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵饼暑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我洗做,道長(zhǎng)弓叛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任诚纸,我火速辦了婚禮撰筷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘畦徘。我一直安慰自己毕籽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布旧烧。 她就那樣靜靜地躺著影钉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掘剪。 梳的紋絲不亂的頭發(fā)上平委,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音夺谁,去河邊找鬼廉赔。 笑死肉微,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜡塌。 我是一名探鬼主播碉纳,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼馏艾!你這毒婦竟也來(lái)了劳曹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤琅摩,失蹤者是張志新(化名)和其女友劉穎铁孵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體房资,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜕劝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轰异。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岖沛。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖搭独,靈堂內(nèi)的尸體忽然破棺而出婴削,到底是詐尸還是另有隱情,我是刑警寧澤戳稽,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布馆蠕,位于F島的核電站,受9級(jí)特大地震影響惊奇,放射性物質(zhì)發(fā)生泄漏互躬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一颂郎、第九天 我趴在偏房一處隱蔽的房頂上張望吼渡。 院中可真熱鬧,春花似錦乓序、人聲如沸寺酪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寄雀。三九已至,卻和暖如春陨献,著一層夾襖步出監(jiān)牢的瞬間盒犹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留急膀,地道東北人沮协。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像卓嫂,于是被迫代替她去往敵國(guó)和親慷暂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炸卑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容