Java直接內存原理

直接內存

在這裏插入圖片描述
在這裏插入圖片描述
上述對直接內存的描述來自《深刻理解Java虛擬機》,寫明瞭直接內存不在java堆內,而且java堆內存往外寫須要拷貝到native堆。

而後我們先寫個代碼看看直接內存分配在哪一個區域

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/** * @author congzhou * @description: * @date: Created in 2019/2/19 21:57 */
public class NativeHeapTest {
    public static void main(String[] args) throws InterruptedException {
        List<ByteBuffer> byteBufferList = new ArrayList<>();
        while (true) {
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10240);
            byteBufferList.add(byteBuffer);
            Thread.sleep(100);
        }
    }
}
複製代碼

watch命令觀察變化內存 html

在這裏插入圖片描述
我使用的是64位centos7,虛擬地址<00007fffffffffff 是用戶空間內存[1]。ByteBuffer.allocateDirect()分配的是用戶空間的匿名內存。

再來看一下ByteBuffer.allocateDirect(int capacity)

ByteBuffer.allocateDirect調用了Unsafe.allocateMemory(long var1),該方法是個native方法 github.com/unofficial-…java

UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory0(JNIEnv *env, jobject unsafe, jlong size)) {
  size_t sz = (size_t)size;

  sz = align_up(sz, HeapWordSize);
  void* x = os::malloc(sz, mtOther);

  return addr_to_java(x);
} UNSAFE_END
複製代碼

主要就是os::malloc(sz, mtInternal)方法,該方法封裝了C++的標準庫std::malloc() github.com/unofficial-…linux

void* os::malloc(size_t size, MEMFLAGS flags) {
  return os::malloc(size, flags, CALLER_PC);
}

void* os::malloc(size_t size, MEMFLAGS memflags, const NativeCallStack& stack) {
/*省略代碼*/
  u_char* ptr;
  ptr = (u_char*)::malloc(alloc_size);
/*省略代碼*/
  // we do not track guard memory
  return MemTracker::record_malloc((address)ptr, size, memflags, stack, level);
}
複製代碼

(u_char*)::malloc(alloc_size)就是C++標準庫函數,傳入空間大小返回內存地址。git

那麼堆內存怎麼往外寫

openJDK sun.nio.ch.IOUtil.write(),java.nio.channels.SocketChannel#write(java.nio.ByteBuffer)等方法底層實現github

static int write(FileDescriptor fd, ByteBuffer src, long position, NativeDispatcher nd) throws IOException {
        if (src instanceof DirectBuffer)
            return writeFromNativeBuffer(fd, src, position, nd);

        // Substitute a native buffer
        int pos = src.position();
        int lim = src.limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);
        ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);
        try {
            bb.put(src);
            bb.flip();
            // Do not update src until we see how many bytes were written
            src.position(pos);

            int n = writeFromNativeBuffer(fd, bb, position, nd);
            if (n > 0) {
                // now update src
                src.position(pos + n);
            }
            return n;
        } finally {
            Util.offerFirstTemporaryDirectBuffer(bb);
        }
    }
複製代碼

上述代碼直觀的展現了java堆內存首先拷貝到了直接內存,而後再把地址傳給I/O函數。
java GC三大類算法,除了標記清除,標記整理和複製算法都會移動對象,而且若是直接把java堆地址傳給I/O函數則須要保證I/O操做過程當中該塊內存不變化,則須要暫停GC,因此JDK實現使用拷貝的方式。算法

操做系統I/O過程當中,須要把數據從用戶態拷貝到內核態,而後再輸出到I/O設備,因此從java堆內存輸出到I/O設備需通過兩次拷貝,而direct memory在native 堆上,因此只需通過一次拷貝。centos

[1]www.kernel.org/doc/Documen…
[2]www.linuxjournal.com/article/634…
[3]www.ibm.com/developerwo…
[4]zhuanlan.zhihu.com/p/27625923
[5]www.zhihu.com/question/57…函數

相關文章
相關標籤/搜索