Netty 零拷贝(二)NIO 对零拷贝的支持

Netty 之美系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html)

相关文章:

  • 非直接缓冲区(HeapByteBuffer):在 JVM 内存上分配一个字节数组 byte[] hb
  • 直接缓冲区(DirectByteBuffer):保存一个指向系统内核的地址 long address

一、非直接缓冲区和直接缓冲区

(1) Buffer 分配

// 分配非直接缓冲区
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
} // 分配直接缓冲区
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}

(2) ByteBuffer 内存存储

public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity; // 直接缓冲区指向系统内核的一个地址,之所以放到父类中是为了加快 JNI 的访问速度
long address;
} public abstract class ByteBuffer extends Buffer {
// 非直接缓冲区在 JVM 内存上分配一个字节数组
final byte[] hb;
}

(3) DirectByteBuffer

DirectByteBuffer(int cap) {
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)) {
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null
} public byte get() {
return ((unsafe.getByte(ix(nextGetIndex()))));
} public ByteBuffer put(byte x) {
unsafe.putByte(ix(nextPutIndex()), ((x)));
return this;
}

DirectByteBuffer 对直接缓冲区的操作都委托给了类 sun.misc.Unsafe,Unsafe 都是一些本地方法 native。

public final class Unsafe {
public native long allocateMemory(long bytes);
public native byte getByte(long address);
public native void putByte(long address, byte x);
}

二、直接缓冲区应用

使用直接缓冲区可以避免用户空间和系统空间之间的拷贝过程,即零拷贝。

FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("3.png"),
StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); // 方式一:内存映射文件,直接缓冲区
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
//只有 READ_WRITE,没有 WRITE,因此 outChannel 也要加上 READ
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size()); byte[] bytes = new byte[inMappedBuf.limit()];
inMappedBuf.get(bytes);
outMappedBuf.put(bytes); // 方式二:transferTo 也是使用直接缓冲区
//inChannel.transferTo(0, inChannel.size(), outChannel);
//outChannel.transferFrom(inChannel, 0, inChannel.size());

三、DirectByteBuffer

Java NIO中的 direct buffer(主要是 DirectByteBuffer)其实是分两部分的:

       Java        |      native
|
DirectByteBuffer | malloc'd
[ address ] -+-> [ data ]

其中 DirectByteBuffer 自身是一个 Java 对象,在 Java 堆中;而这个对象中有个 long 类型字段 address,记录着一块调用 malloc() 申请到的 native memory。

FileChannel 的 read(ByteBuffer dst) 函数,write(ByteBuffer src) 函数中,如果传入的参数是 HeapBuffer 类型,则会临时申请一块 DirectBuffer,进行数据拷贝,而不是直接进行数据传输,这是出于什么原因?

// OpenJDK 的 sun.nio.ch.IOUtil
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);
}
}

这里其实是在迁就 OpenJDK 里的 HotSpot VM 的一点实现细节。

HotSpot VM 里的 GC 除了 CMS 之外都是要移动对象的,是所谓 “compacting GC”。

如果要把一个 Java 里的 byte[] 对象的引用传给 native 代码,让 native 代码直接访问数组的内容的话,就必须要保证 native 代码在访问的时候这个 byte[] 对象不能被移动,也就是要被“pin”(钉)住。

可惜 HotSpot VM 出于一些取舍而决定不实现单个对象层面的 object pinning,要 pin 的话就得暂时禁用 GC ——也就等于把整个 Java 堆都给 pin 住。HotSpot VM 对 JNI 的 Critical 系 API 就是这样实现的。这用起来就不那么顺手。

所以 Oracle/Sun JDK / OpenJDK 的这个地方就用了点绕弯的做法。 它假设把 HeapByteBuffer 背后的 byte[] 里的内容拷贝一次是一个时间开销可以接受的操作,同时假设真正的 I/O 可能是一个很慢的操作。

于是它就先把 HeapByteBuffer 背后的 byte[] 的内容拷贝到一个 DirectByteBuffer 背后的 native memory 去,这个拷贝会涉及 sun.misc.Unsafe.copyMemory() 的调用,背后是类似 memcpy() 的实现。这个操作本质上是会在整个拷贝过程中暂时不允许发生 GC 的,虽然实现方式跟 JNI 的 Critical 系 API 不太一样。(具体来说是 Unsafe.copyMemory() 是 HotSpot VM 的一个 intrinsic 方法,中间没有 safepoint 所以 GC 无法发生)。

然后数据被拷贝到 native memory 之后就好办了,就去做真正的 I/O,把 DirectByteBuffer 背后的 native memory 地址传给真正做 I/O 的函数。这边就不需要再去访问 Java 对象去读写要做 I/O 的数据了。

参考:

  1. 《Java NIO中,关于DirectBuffer,HeapBuffer的疑问?》:https://www.zhihu.com/question/57374068/answer/152691891

每天用心记录一点点。内容也许不重要,但习惯很重要!

Netty 零拷贝(一)NIO 对零拷贝的支持的更多相关文章

  1. Netty 零拷贝(三)Netty 对零拷贝的改进

    Netty 零拷贝(三)Netty 对零拷贝的改进 Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html) Netty 的&quo ...

  2. Netty 零拷贝(一)Linux 零拷贝

    Netty 零拷贝(一)Linux 零拷贝 本文探讨 Linux 中主要的几种零拷贝技术以及零拷贝技术适用的场景. 一.几个重要的概念 1.1 用户空间与内核空间 操作系统的核心是内核,独立于普通的应 ...

  3. Netty学习笔记(一)——nio基础

    Netty简单认识: 1) Netty 是由JBOSS 提供的一个Java 开源框架. 2) Netty 是一个异步的.基于事件驱动的网络应用框架,用以快速开发高性能.高可靠性的网络I0 程序. 3) ...

  4. c++的默认构造函数 VS 深拷贝(值拷贝) 与 浅拷贝(位拷贝)

    C++默认为类生成了四个缺省函数: A(void); // 缺省的无参数构造函数 A(const A &a); // 缺省的拷贝构造函数 ~A(void); // 缺省的析构函数 A & ...

  5. C++ //构造函数调用规则 //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) //析构函数(空实现) //拷贝函数(值拷贝) //2.如果我们写了有参构造函数 编译器就不会提供默认构造函数 但是会提供拷贝构造函数 //3.如果我们写了拷贝函数 编译器就不再提供 默认 有参 构造函数

    //构造函数调用规则 #include <iostream> using namespace std; //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) ...

  6. NIO学习笔记,从Linux IO演化模型到Netty—— Netty零拷贝

    Netty的中零拷贝与上述零拷贝是不一样的,它并不是系统层面上的零拷贝,只是相对于ByteBuf而言的,更多的是偏向于数据操作优化这样的概念. Netty中的零拷贝: 1.CompositeByteB ...

  7. Java后端进阶-网络编程(Netty零拷贝机制)

    package com.study.hc.net.netty.demo; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled ...

  8. java IO Nio 文件拷贝工具类Files

    public static void main(String[] args) throws Exception { Files.copy(Paths.get("file/text.txt&q ...

  9. 文件拷贝io nio比较

    import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedRead ...

随机推荐

  1. C# DbHelperSQL 类,从东软生成器提取而来

    DBHelerSQL,可以结合东软生成器写简单三层结构,也可以自己去调用执行相关SQL语句 直接上代码: using System; using System.Collections; using S ...

  2. java8函数式编程(转载)

    1. 概述 1.1 函数式编程简介 我们最常用的面向对象编程(Java)属于命令式编程(Imperative Programming)这种编程范式.常见的编程范式还有逻辑式编程(Logic Progr ...

  3. 基于request.getAttribute与request.getParameter的区别详解

    HttpServletRequest类既有getAttribute()方法,也有getParameter()方法,这两个方法有以下区别:1.HttpServletRequest类有setAttribu ...

  4. jsp 调用其他jsp页面 跳转

    response.sendRedirect("test2.jsp"); window.location.reload("test2.jsp"); locatio ...

  5. 基于OpenGL编写一个简易的2D渲染框架-13 使用例子

    这是重构渲染器的最后一部分了,将会给出一个 demo,测试模板测试.裁剪测试.半透明排序等等: 上图是本次 demo 的效果图,中间的绿色图形展现的是模板测试. 模板测试 void init(Pass ...

  6. UI5-文档-4.26-(Optional) Remote OData Service

    到目前为止,我们已经使用了本地JSON数据,但是现在我们将访问一个真正的OData服务来可视化远程数据. 用可公开获得的Northwind OData服务显示并替换发票模型的JSONModel类型,以 ...

  7. vue -本地服务配置

    1.httpd.conf # # This is the main Apache HTTP server configuration file. It contains the # configura ...

  8. Java时间操作常用api

    - 如何取得年月日.小时分钟秒?- 如何取得从1970年1月1日0时0分0秒到现在的毫秒数?- 如何取得某月的最后一天?- 如何格式化日期?答:问题1:创建java.util.Calendar 实例, ...

  9. HIBERNATE知识复习记录2-继承关系

    发现了一篇和我类似的学习尚硅谷视频写的文章,内容如下,比我说的详细全面,可以看一下: [原创]java WEB学习笔记87:Hibernate学习之路-- -映射 继承关系(subclass , jo ...

  10. TEXT 8 Ready, fire, aim

    TEXT 8 Ready, fire, aim 预备!开火!瞄准!! Feb 16th 2006 From The Economist print edition Foreword:A vice-pr ...