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. wzben的QQ空间

    实习之后没有动过博客了,后续慢慢补.

  2. leetcode942

    public class Solution { public int[] DiStringMatch(string S) { var len = S.Length; ; var max = len; ...

  3. leetcode292

    public class Solution { public bool CanWinNim(int n) { //如果要赢,最后3个必须是自己来拿 //也就是最后剩1,2,3是胜利, //如果剩4枚, ...

  4. Windows 域用户

    Windows 2000 组及说明 分类: Windows 2000 的组分为Security 和 Distribution 两种. Security 类型是Windows 2000 唯一用于赋予权限 ...

  5. 可视化库-seaborn-布局风格设置(第五天)

    1. sns.set_style() 进行风格设置, sns.set() 进行设置的重置, 五种风格 # 1.darkgrid# 2.whitegrid# 3.dark# 4.white# 5 tic ...

  6. python 1 面向对象基础知识

    1.编码范式 编程  是程序员用特定的 语法+数据结构+算法 组成的代码来告诉计算机如何执行任务的过程 如果把编程比作习武,编程方式就是武林中的各种流派,而在编程的世界里面最常见的两大流派是:面向过程 ...

  7. VBox 安装 macOS 10.12

    安装步骤⑴ 下载及解压 macOS 10.12 Sierra Final by TechReviews.rar ⑵ 下载及双击安装 VirtualBox-5.1.6-110634-Win.exe ,默 ...

  8. Hystrix-超时设置

    由于客户端请求服务端方法时,服务端方法响应超过1秒将会触发降级,所以我们可以配置Hystrix默认的超时配置 如果我们没有配置默认的超时时间,Hystrix将取default_executionTim ...

  9. SQL2008R2 express版本不支持维护计划

    SQL2008R2 express版本不支持维护计划

  10. (转)游戏引擎中三大及时光照渲染方法介绍(以unity3d为例)

    重要:在目前市面上常见的游戏引擎中,主要采用以下三种灯光实现方式: 顶点照明渲染路径细节 Vertex Lit Rendering Path Details 正向渲染路径细节 Forward Rend ...