05-Netty的零拷贝技术

零拷贝

堆内存和直接内存

什么是直接内存

要说零拷贝,那我们先要知道什么是直接内存和堆内存。所谓堆内存就是分配给JVM堆区的内存,而直接内存是java通过调用navite方法可以直接分配JVM之外的内存区域,所以我们也把这块内存叫做堆外内存。而我们可以在堆区通过一个DirectByteBuffer去引用这块内存。因为这块内存是没有交给JVM去管理的,比较容易发生内存溢出,为了避免一直没有FULL GC,导致耗完物理内存,我们可以通过参数-XX:MaxDirectMemorySize 指定直接内存的大小,这样当直接内存到达一个阈值的时候,就会被动触发Full GC。直接内存和堆内存的关系如下所示:

在这里插入图片描述

JDK给我们提供了Api可以查看默认的堆外内存大小:

System.out.println("direct memory : "+ VM.maxDirectMemory() / 1024 / 1024);
System.out.println("heap memory : "+ Runtime.getRuntime().maxMemory() / 1024 / 1024);
直接内存如何申请使用

那么了解了什么是直接内存,我们接着看一下如何使用直接内存,代码如下:

// 通过这个方法我们可以申请1000字节的直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(1000);

// 跟踪这个方法到DirectByteBuffer.java:120行,我们看到他是调用了一个navite方法去申请的内存,返回来的是一个地址
// 如果有openJDK的代码,可以看到这个方法底层的代码
base = unsafe.allocateMemory(size);

// 这是navite方法调用操作系统的方法为我们申请内存  unsafe.cpp:628
void* x = os::malloc(sz, mtInternal);
直接内存和堆内存对比

下面这段代码通过分别对堆内存和直接内存的反复申请读写,测试了一下耗时,发现直接内存的访问性能普遍高于堆内存的访问性能,这是因为使用直接内存只需要一次拷贝,而使用堆内存需要两次拷贝,详情参考下文零拷贝。而直接内存的申请效率普遍低于堆内存的申请。

public class MemoryTest {

    /**
     * 操作堆内存
     */
    private static void heapAccess() {
        long startTime = System.currentTimeMillis();
        // 分配堆内存
        ByteBuffer buffer = ByteBuffer.allocate(1000);
        readAndWrite(buffer);
        long endTime = System.currentTimeMillis();
        System.out.println("堆内存访问:" + (endTime - startTime) + "ms");
    }

    /**
     * 操作直接内存
     */
    private static void directAccess() {
        long startTime = System.currentTimeMillis();
        // 分配直接内存
        ByteBuffer buffer = ByteBuffer.allocateDirect(1000);
        readAndWrite(buffer);
        long endTime = System.currentTimeMillis();
        System.out.println("直接内存访问:" + (endTime - startTime) + "ms");
    }

    private static void readAndWrite(ByteBuffer buffer) {
        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();
        }
    }

    private 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) + "ms");
    }

    private 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) + "ms");
    }

    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();
        }
    }
}

// 执行结果
堆内存访问:54ms
直接内存访问:151ms
堆内存访问:84ms
直接内存访问:41ms
堆内存访问:78ms
直接内存访问:40ms
堆内存访问:83ms
直接内存访问:40ms
堆内存访问:99ms
直接内存访问:49ms
堆内存访问:166ms
直接内存访问:55ms
堆内存访问:123ms
直接内存访问:54ms
堆内存访问:102ms
直接内存访问:49ms
堆内存访问:79ms
直接内存访问:39ms
堆内存访问:78ms
直接内存访问:40ms
---------------------------
堆内存申请:13ms
直接内存申请:71ms
堆内存申请:6ms
直接内存申请:23ms
堆内存申请:23ms
直接内存申请:29ms
堆内存申请:2ms
直接内存申请:28ms
堆内存申请:7ms
直接内存申请:77ms
堆内存申请:3ms
直接内存申请:26ms
堆内存申请:2ms
直接内存申请:21ms
堆内存申请:213ms
直接内存申请:41ms
堆内存申请:2ms
直接内存申请:40ms
堆内存申请:2ms
直接内存申请:38ms

零拷贝

在这里插入图片描述

何为零拷贝呢,不是说一次拷贝都没有,而是说相比较操作堆内存少了两次拷贝。平时发生一次读写,如果是操作堆内存的话,往往需要四次拷贝,读的时候将数据从直接内存拷贝到堆内存,再从堆内存拷贝到缓冲区,写的时候将数据从缓冲区拷贝到堆内存,在从堆内存拷贝到直接内存。而操作直接内存就不一样了,少去了堆内存和直接内存之间的拷贝过程。我们把这个叫做零拷贝。而Netty接受和发送数据用了直接内存来提升效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半__夏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值