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命令观察变化内存
在这里插入图片描述
我使用的是64位centos7,虚拟地址<00007fffffffffff 是用户空间内存[1]。ByteBuffer.allocateDirect()分配的是用户空间的匿名内存。

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

ByteBuffer.allocateDirect调用了Unsafe.allocateMemory(long var1),该方法是个native方法
https://github.com/unofficial-openjdk/openjdk/blob/jdk/jdk/src/hotspot/share/prims/unsafe.cpp

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()
https://github.com/unofficial-openjdk/openjdk/blob/jdk/jdk/src/hotspot/share/runtime/os.cpp

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++标准库函数,传入空间大小返回内存地址。

那么堆内存怎么往外写

openJDK sun.nio.ch.IOUtil.write(),java.nio.channels.SocketChannel#write(java.nio.ByteBuffer)等方法底层实现

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 堆上,所以只需经过一次拷贝。

[1]https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt
[2]https://www.linuxjournal.com/article/6345?page=0,1
[3]https://www.ibm.com/developerworks/library/j-nativememory-linux/index.html
[4]https://zhuanlan.zhihu.com/p/27625923
[5]https://www.zhihu.com/question/57374068/answer/152691891

猜你喜欢

转载自blog.csdn.net/zxcc1314/article/details/87826665
今日推荐