Netty之OutOfDirectMemoryError

    最近使用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的

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章