【深刻Java编程】JVM源码分析之堆外内存彻底解读

概述

广义的堆外内存

说到堆外内存,那你们确定想到堆内内存,这也是咱们你们接触最多的,咱们在jvm参数里一般设置-Xmx来指定咱们的堆的最大值,不过这还不是咱们理解的Java堆,-Xmx的值是新生代和老生代的和的最大值,咱们在jvm参数里一般还会加一个参数-XX:MaxPermSize来指定持久代的最大值,那么咱们认识的Java堆的最大值实际上是-Xmx和-XX:MaxPermSize的总和,在分代算法下,新生代,老生代和持久代是连续的虚拟地址,由于它们是一块儿分配的,那么剩下的均可以认为是堆外内存(广义的)了,这些包括了jvm自己在运行过程当中分配的内存,codecache,jni里分配的内存,DirectByteBuffer分配的内存等等java

狭义的堆外内存

而做为java开发者,咱们常说的堆外内存溢出了,实际上是狭义的堆外内存,这个主要是指java.nio.DirectByteBuffer在建立的时候分配内存,咱们这篇文章里也主要是讲狭义的堆外内存,由于它和咱们平时碰到的问题比较密切算法

JDK/JVM里DirectByteBuffer的实现

DirectByteBuffer一般用在通讯过程当中作缓冲池,在mina,netty等nio框架中家常便饭,先来看看JDK里的实现:框架

DirectByteBuffer(int cap) {                   // package-private

        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;



    }

经过上面的构造函数咱们知道,真正的内存分配是使用的Bits.reserveMemory方法jvm

static void reserveMemory(long size, int cap) {
        synchronized (Bits.class) {
            if (!memoryLimitSet && VM.isBooted()) {
                maxMemory = VM.maxDirectMemory();
                memoryLimitSet = true;
            }
            // -XX:MaxDirectMemorySize limits the total capacity rather than the
            // actual memory usage, which will differ when buffers are page
            // aligned.
            if (cap <= maxMemory - totalCapacity) {
                reservedMemory += size;
                totalCapacity += cap;
                count++;
                return;
            }
        }

        System.gc();
        try {
            Thread.sleep(100);
        } catch (InterruptedException x) {
            // Restore interrupt status
            Thread.currentThread().interrupt();
        }
        synchronized (Bits.class) {
            if (totalCapacity + cap > maxMemory)
                throw new OutOfMemoryError("Direct buffer memory");
            reservedMemory += size;
            totalCapacity += cap;
            count++;
        }

    }

经过上面的代码咱们知道能够经过-XX:MaxDirectMemorySize来指定最大的堆外内存,那么咱们首先引入两个问题ide

  • 堆外内存默认是多大
  • 为何要主动调用System.gc()

堆外内存默认是多大

若是咱们没有经过-XX:MaxDirectMemorySize来指定最大的堆外内存,那么默认的最大堆外内存是多少呢,咱们仍是经过代码来分析函数

上面的代码里咱们看到调用了sun.misc.VM.maxDirectMemory()this

private static long directMemory = 64 * 1024 * 1024;

    // 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;
    }

看到上面的代码以后是否是误觉得默认的最大值是64M?其实不是的,说到这个值得从java.lang.System这个类的初始化提及spa

/**
     * Initialize the system class.  Called after thread initialization.
     */
    private static void initializeSystemClass() {

        // VM might invoke JNU_NewStringPlatform() to set those encoding
        // sensitive properties (user.home, user.name, boot.class.path, etc.)
        // during "props" initialization, in which it may need access, via
        // System.getProperty(), to the related system encoding property that
        // have been initialized (put into "props") at early stage of the
        // initialization. So make sure the "props" is available at the
        // very beginning of the initialization and all system properties to
        // be put into it directly.
        props = new Properties();
        initProperties(props);  // initialized by the VM

        // There are certain system configurations that may be controlled by
        // VM options such as the maximum amount of direct memory and
        // Integer cache size used to support the object identity semantics
        // of autoboxing.  Typically, the library will obtain these values
        // from the properties set by the VM.  If the properties are for
        // internal implementation use only, these properties should be
        // removed from the system properties.
        //
        // See java.lang.Integer.IntegerCache and the
        // sun.misc.VM.saveAndRemoveProperties method for example.
        //
        // Save a private copy of the system properties object that
        // can only be accessed by the internal implementation.  Remove
        // certain system properties that are not intended for public access.
        sun.misc.VM.saveAndRemoveProperties(props);

         ......

        sun.misc.VM.booted();
    }

上面这个方法在jvm启动的时候对System这个类作初始化的时候执行的,所以执行时间很是早,咱们看到里面调用了sun.misc.VM.saveAndRemoveProperties(props):netty

阅读全文直接点击:http://click.aliyun.com/m/9972/code

相关文章
相关标签/搜索