天天看點

JVM源碼分析之不可控的堆外記憶體

打開directbytebuffer這個類,我們會發現有5個構造函數

我們從java層面建立directbytebuffer對象,一般都是通過bytebuffer的allocatedirect方法

也就是會使用上面提到的第一個構造函數,即

而這個構造函數裡的<code>bits.reservememory(size, cap)</code>方法會做堆外記憶體的門檻值check

是以當我們已經配置設定的記憶體超過門檻值的時候會觸發一次gc動作,并重新做一次配置設定,如果還是超過門檻值,那将會抛出oom,是以配置設定動作會失敗。

是以從這一切看來,隻要設定了<code>-xx:maxdirectmemorysize=1g</code>是不會出現超過這個門檻值的情況的,會看到不斷的做gc。

那其他的構造函數主要是用在什麼情況下的呢?

我們知道directbytebuffer回收靠的是裡面有個cleaner的屬性,但是我們發現有幾個構造函數裡cleaner這個屬性卻是null,那這種情況下他們怎麼被回收呢?

那下面請大家先看下directbytebuffer裡的這兩個函數:

從名字和實作上基本都能猜出是幹什麼的了,slice其實是從一塊已知的記憶體裡取出剩下的一部分,用一個新的directbytebuffer對象指向它,而duplicate就是建立一個現有directbytebuffer的全新副本,各種指針都一樣。

是以從這個實作來看,後面關聯的堆外記憶體其實是同一塊,是以如果我們做統計的時候如果僅僅将所有directbytebuffer對象的capacity加起來,那可能會導緻算出來的結果偏大不少,這其實也是我查的那個問題,本來設定了門檻值1g,但是發現達到了7g的效果。是以這種情況下使用的構造函數,可以讓cleaner為null,回收靠原來的那個directbytebuffer對象被回收。

但是還有種情況,也是本文要講的重點,在jvm裡可以通過jni方法回調上面的directbytebuffer構造函數,這個構造函數是

而調用這個構造函數的jni方法是<code>jni_newdirectbytebuffer</code>

想象這麼種情況,我們寫了一個native方法,裡面配置設定了一塊記憶體,同時通過上面這個方法和一個directbytebuffer對象關聯起來,那從java層面來看這個directbytebuffer确實是一個有效的占有不少native記憶體的對象,但是這個對象後面關聯的記憶體完全繞過了maxdirectmemorysize的check,是以也可能給你造成這種現象,明明設定了maxdirectmemorysize,但是發現directbytebuffer關聯的堆外記憶體其實是大于它的。

個人公衆号:

JVM源碼分析之不可控的堆外記憶體