最近使用Netty時,由於JVM參數設置錯誤,有OutOfDirectMemoryError出現,記錄下:
OutOfDirectMemoryError是在分配堆外內存時發生的,由於堆外內存不夠,導致拋出異常,如下List-1,PlatformDependent中的
List-1
private static void incrementMemoryCounter(int capacity) {
if (DIRECT_MEMORY_COUNTER != null) {
for (;;) {
long usedMemory = DIRECT_MEMORY_COUNTER.get();
long newUsedMemory = usedMemory + capacity;
if (newUsedMemory > DIRECT_MEMORY_LIMIT) {
throw new OutOfDirectMemoryError("failed to allocate " + capacity
+ " byte(s) of direct memory (used: " + usedMemory + ", max: " + DIRECT_MEMORY_LIMIT + ')');
}
if (DIRECT_MEMORY_COUNTER.compareAndSet(usedMemory, newUsedMemory)) {
break;
}
}
}
}
使用一個AtomicLong類型的變量DIRECT_MEMORY_COUNTER來存儲目前分配了多少堆外內存,每次申請堆外內存,先判斷下目前已經申請的+要申請的是否大於Direct memeory大小限制,如果超過,那麼就拋出異常;對應的,釋放堆外內存時,從DIRECT_MEMORY_COUNTER中減去對應的大小。
那麼DIRECT_MEMORY_LIMIT的值是怎麼獲取的?
List-2
static {
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.noPreferDirect: {}", !DIRECT_BUFFER_PREFERRED);
}
if (!hasUnsafe() && !isAndroid() && !IS_EXPLICIT_NO_UNSAFE) {
logger.info(
"Your platform does not provide complete low-level API for accessing direct buffers reliably. " +
"Unless explicitly requested, heap buffer will always be preferred to avoid potential system " +
"unstability.");
}
// Here is how the system property is used:
//
// * < 0 - Don't use cleaner, and inherit max direct memory from java. In this case the
// "practical max direct memory" would be 2 * max memory as defined by the JDK.
// * == 0 - Use cleaner, Netty will not enforce max memory, and instead will defer to JDK.
// * > 0 - Don't use cleaner. This will limit Netty's total direct memory
// (note: that JDK's direct memory limit is independent of this).
long maxDirectMemory = SystemPropertyUtil.getLong("io.netty.maxDirectMemory", -1);
if (maxDirectMemory == 0 || !hasUnsafe() || !PlatformDependent0.hasDirectBufferNoCleanerConstructor()) {
USE_DIRECT_BUFFER_NO_CLEANER = false;
DIRECT_MEMORY_COUNTER = null;
} else {
USE_DIRECT_BUFFER_NO_CLEANER = true;
if (maxDirectMemory < 0) {
maxDirectMemory = maxDirectMemory0();
if (maxDirectMemory <= 0) {
DIRECT_MEMORY_COUNTER = null;
} else {
DIRECT_MEMORY_COUNTER = new AtomicLong();
}
} else {
DIRECT_MEMORY_COUNTER = new AtomicLong();
}
}
DIRECT_MEMORY_LIMIT = maxDirectMemory;
logger.debug("io.netty.maxDirectMemory: {} bytes", maxDirectMemory);
}
如上List-2,
首先從io.netty.maxDirectMemory設置的SystemProperty中獲取,如果這個值大於0,說明設置了最大堆外內存,直接使用。
如果沒有設置io.netty.maxDirectMemory,那麼獲取maxDirectMemory0()方法來獲取:
- 先獲取sun包得到VM類獲取最大內存,如果值大於0,那麼返還使用
- 如果獲取不到,則獲取JVM參數MaxDirectMemorySize的值,即我們可以通過jvm參數指定使用最大多少堆外內存
- 如果還是沒有獲取得到,則使用Runtime.getRuntime().maxMemory()的值
List-3
private static long maxDirectMemory0() {
long maxDirectMemory = 0;
try {
// Try to get from sun.misc.VM.maxDirectMemory() which should be most accurate.
Class<?> vmClass = Class.forName("sun.misc.VM", true, getSystemClassLoader());
Method m = vmClass.getDeclaredMethod("maxDirectMemory");
maxDirectMemory = ((Number) m.invoke(null)).longValue();
} catch (Throwable ignored) {
// Ignore
}
if (maxDirectMemory > 0) {
return maxDirectMemory;
}
try {
// Now try to get the JVM option (-XX:MaxDirectMemorySize) and parse it.
// Note that we are using reflection because Android doesn't have these classes.
Class<?> mgmtFactoryClass = Class.forName(
"java.lang.management.ManagementFactory", true, getSystemClassLoader());
Class<?> runtimeClass = Class.forName(
"java.lang.management.RuntimeMXBean", true, getSystemClassLoader());
Object runtime = mgmtFactoryClass.getDeclaredMethod("getRuntimeMXBean").invoke(null);
@SuppressWarnings("unchecked")
List<String> vmArgs = (List<String>) runtimeClass.getDeclaredMethod("getInputArguments").invoke(runtime);
for (int i = vmArgs.size() - 1; i >= 0; i --) {
Matcher m = MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN.matcher(vmArgs.get(i));
if (!m.matches()) {
continue;
}
maxDirectMemory = Long.parseLong(m.group(1));
switch (m.group(2).charAt(0)) {
case 'k': case 'K':
maxDirectMemory *= 1024;
break;
case 'm': case 'M':
maxDirectMemory *= 1024 * 1024;
break;
case 'g': case 'G':
maxDirectMemory *= 1024 * 1024 * 1024;
break;
}
break;
}
} catch (Throwable ignored) {
// Ignore
}
if (maxDirectMemory <= 0) {
maxDirectMemory = Runtime.getRuntime().maxMemory();
logger.debug("maxDirectMemory: {} bytes (maybe)", maxDirectMemory);
} else {
logger.debug("maxDirectMemory: {} bytes", maxDirectMemory);
}
return maxDirectMemory;
}
List-3裏,如果我們的機器有8G運行內存,那麼sun的VM類返還maxDirectMemory的值會是8G嗎,代碼實際執行的結果來看,是不會的,是小於8G的