美文网首页
关于java直接内存

关于java直接内存

作者: 李亚林1990 | 来源:发表于2019-01-25 14:31 被阅读44次

java1.4引入了nio.DirectByteBuffer,可能大部分同学在工作中跟直接内存打交道的并不多,本篇将结合网上资料查阅和自己的理解来对java直接内存和非直接内存做一个细致的整理。如有不合理之处,欢迎评论区留言。

Netty服务端接收到消息体存储在ByteBuf中,接下来我们通过Debug的方式来跟踪一下ByteBuff的内存分配方式。
客户端发送的消息体在服务端AbstractNioByteChannel.read()中接收处理,在此处断点跟踪:


image.png

留意到PooledByteBufAllocator的属性:directByDefault为true;
进入byteBuf = allocHandle.allocate(allocator);


image.png
image.png
image.png

预执行表达式PlatformDependent.hasUnsafe()返回true,进入directBuffer内存申请;
继续往下看:


image.png
此处先做个最大申请内存的校验,最终我们跟踪到申请直接内存的相关代码:
image.png
从方法说明可以看到:直接内存申请后必须调用freeDirectNoCleaner来手动释放内存

PlatformDependent0中相关代码逻辑如下:

    //调用sun.misc.Unsafe分配直接内存
    static ByteBuffer allocateDirectNoCleaner(int capacity) {
        return newDirectBuffer(UNSAFE.allocateMemory(capacity), capacity);
    }
    //根据申请的内存地址和内存大小创建DerectByteBuff对象
    static ByteBuffer newDirectBuffer(long address, int capacity) {
        ObjectUtil.checkPositiveOrZero(capacity, "capacity");

        try {
            return (ByteBuffer) DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, capacity);
        } catch (Throwable cause) {
            // Not expected to ever throw!
            if (cause instanceof Error) {
                throw (Error) cause;
            }
            throw new Error(cause);
        }
    }

关于直接内存的分配我们用到了sum.misc.Unsafe,类中包含了大量的native方法。研究过jdk源码的同学会发现该类在很多jdk类中都有用到,比如AQS用到的Unsafe.park/Unsafe.unpark、原子操作compareAndSwapXXX、以及此处的allocateMemory/freeMemory等等

通过以上的代码跟踪我们看到Netty关于消息体的存储使用了直接内存的分配方式,这样做的优势在哪呢?
我们来做一个性能测试:

public class MemoryTest {

    public static void heapAccess() {
        long startTime = System.currentTimeMillis();
        ByteBuffer buffer = ByteBuffer.allocate(1000);
        for (int i = 0; i < 100000; i++) {
            for (int j = 0; j < 200; j++) {
                buffer.putInt(j);
            }
            buffer.flip();
            for (int j = 0; j < 200; j++) {
                buffer.getInt();
            }
            buffer.clear();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("堆内存访问:" + (endTime - startTime));
    }

   public static void directAccess() {
        long startTime = System.currentTimeMillis();
        ByteBuffer buffer = ByteBuffer.allocateDirect(1000);
        for (int i = 0; i < 100000; i++) {
            for (int j = 0; j < 200; j++) {
                buffer.putInt(j);
            }
            buffer.flip();
            for (int j = 0; j < 200; j++) {
                buffer.getInt();
            }
            buffer.clear();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("直接内存访问:" + (endTime - startTime));
    }

    public static void heapAllocate() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            ByteBuffer.allocate(100);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("堆内存申请:" + (endTime - startTime));
    }

    public static void directAllocate() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            ByteBuffer.allocateDirect(100);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("直接内存申请:" + (endTime - startTime));
    }

    public static void main(String args[]) {
        for (int i = 0; i < 10; i ++) {
            heapAccess();
            directAccess();
        }

        System.out.println();

        for (int i = 0; i < 10; i ++) {
            heapAllocate();
            directAllocate();
        }
    }
}

输出结果:
堆内存访问:62
直接内存访问:48
堆内存访问:39
直接内存访问:26
堆内存访问:61
直接内存访问:46
堆内存访问:98
直接内存访问:76
堆内存访问:59
直接内存访问:77
堆内存访问:47
直接内存访问:27
堆内存访问:40
直接内存访问:21
堆内存访问:55
直接内存访问:21
堆内存访问:50
直接内存访问:30
堆内存访问:50
直接内存访问:25

堆内存申请:12
直接内存申请:29
堆内存申请:40
直接内存申请:31
堆内存申请:21
直接内存申请:28
堆内存申请:1
直接内存申请:22
堆内存申请:6
直接内存申请:73
堆内存申请:1
直接内存申请:17
堆内存申请:1
直接内存申请:18
堆内存申请:1
直接内存申请:373
堆内存申请:2
直接内存申请:38
堆内存申请:3
直接内存申请:35

可以看出直接内存申请较慢,但访问效率高。
为什么呢???
回顾一下java内存模型:


image.png

我加上内核空间和用户空间的概念

我的理解是堆内存的申请是直接从已分配的堆空间中取一块出来使用,不经过内存申请系统调用,而直接内存的申请则需要本地方法通过系统调用完成。
笔者尝试用strace命令去跟综了下ByteBuffer.allocateDirect和ByteBuffer.allocate涉及的系统调用,然而并没有发现不一样的地方,感兴趣的朋友欢迎评论区交流。

而关于直接内存和非直接内存的访问效率,我们看看ByteBuffer上的一段官方说明:


image.png

在java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>缺页中断=>硬盘),而非直接内存则需要二次拷贝(堆内存=>缺页中断=>内核空间=>硬盘)。

相关文章

  • 关于java直接内存

    java1.4引入了nio.DirectByteBuffer,可能大部分同学在工作中跟直接内存打交道的并不多,本篇...

  • Java 直接内存

    直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是我们常常频繁的使用,而且也...

  • java直接内存

    直接内存 很多一说起直接内存,就会想到堆外内存。但是从概念上说,两者确实不是一回事。堆外内存,就是堆以外的内存,我...

  • java直接内存

    直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。但是这部分内存也被频繁的使...

  • java直接内存

    直接内存 很多一说起直接内存,就会想到堆外内存。但是从概念上说,两者确实不是一回事。堆外内存,就是堆以外的内存,我...

  • 直接内存与 JVM 源码分析

    直接内存(堆外内存) 直接内存有一种叫法,堆外内存。 直接内存(堆外内存)指的是 Java 应用程序通过直接方式从...

  • 直接内存

    什么是直接内存直接内存就是指:java堆外内存。直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中...

  • Android中缓存理解(一)

    Java GarbageCollection(GC) Java不能像C/C++那样直接对内存进行操作(内存分配和垃...

  • java 虚拟机(jvm)-02-java 内存模型(jmm)介

    运行时内存模型 相关内容参见 java 运行时内存模型 直接内存 特征 直接内存并非 JVMS 定义的标准 Jav...

  • Java 内存管理

    Java可以自动管理内存,比C/C++要方便的多, 但是实际Java 也会出现内存溢出的问题。 关于Java的内存...

网友评论

      本文标题:关于java直接内存

      本文链接:https://www.haomeiwen.com/subject/jdcyjqtx.html