共计 6581 个字符,预计需要花费 17 分钟才能阅读完成。
前言
数据序列化存储,或者数据通过网络传输时,会遇到不可避免将数据转成字节数组的场景。字节数组的读写不会太难,但又有点繁琐,为了防止反复造轮子,jdk 推出了 ByteBuffer 来帮忙咱们操作字节数组;而 netty 是一款以后风行的 java 网络 IO 框架,它外部定义了一个 ByteBuf 来治理字节数组,和 ByteBuffer 大同小异
- ByteBuffer
- 零拷贝之 MappedByteBuffer
- DirectByteBuffer 堆外内存回收机制
- netty 之 ByteBuf
关注公众号,一起交换,微信搜一搜: 潜行前行
Buffer 构造
public abstract class Buffer {
// 关系: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
long address; // Used only by direct buffers,间接内存的地址
- mark:调用 mark()办法的话,mark 值将存储以后 position 的值,等下次调用 reset()办法时,会设定 position 的值为之前的标记值
- position:是下一个要被读写的 byte 元素的下标索引
- limit:是缓冲区中第一个不能读写的元素的数组下标索引,也能够认为是缓冲区中理论元素的数量
- capacity:是缓冲区可能包容元素的最大数量,这个值在缓冲区创立时被设定,而且不可能扭转
Buffer.API
Buffer(int mark, int pos, int lim, int cap)
//Buffer 创立时设置的最大数组容量值
public final int capacity()
// 以后指针的地位
public final int position()
// 限度可读写大小
public final Buffer limit(int newLimit)
// 标记以后 position 的地位
public final Buffer mark()
// 配合 mark 应用,position 成之前 mark()标记的地位。先前没调用 mark 则报错
public final Buffer reset()
// 写 -> 读模式翻转,单向的
//position 变成了初值地位 0,而 limit 变成了写模式下 position 地位
public final Buffer flip()
// 重置 position 指针地位为 0,mark 为 -1;绝对 flip 办法是 limit 不变
public final Buffer rewind() // 复位
// 和 rewind 一样,多出一步是 limit 会被设置成 capacity
public final Buffer clear()
// 返回残余未读字节数
public final int remaining()
ByteBuffer 构造
public abstract class ByteBuffer extends Buffer
implements Comparable<ByteBuffer>{final byte[] hb; // 仅限堆内内存应用
final int offset;
boolean isReadOnly;
ByteBuffer.API
// 申请堆外内存
public static ByteBuffer allocateDirect(int capacity)
// 申请堆内内存
public static ByteBuffer allocate(int capacity)
// 原始字节包装成 ByteBuffer
public static ByteBuffer wrap(byte[] array, int offset, int length)
// 原始字节包装成 ByteBuffer
public static ByteBuffer wrap(byte[] array)
// 创立共享此缓冲区内容的新字节缓冲区
public abstract ByteBuffer duplicate()
// 分片,创立一个新的字节缓冲区
// 新 ByteBuffer 的开始地位是此缓冲区的以后地位 position
public abstract ByteBuffer slice()
// 获取字节内容
public abstract byte get()
// 从 ByteBuffer 偏移 offset 的地位,获取 length 长的字节数组,而后返回以后 ByteBuffer 对象
public ByteBuffer get(byte[] dst, int offset, int length)
// 设置 byte 内存
public abstract ByteBuffer put(byte b);
// 以 offset 为起始地位设置 length 长 src 的内容,并返回以后 ByteBuffer 对象
public ByteBuffer put(byte[] src, int offset, int length 长)
// 将没有读完的数据移到到缓冲区的初始地位,position 设置为最初一没读字节数据的下个索引,limit 重置为为 capacity
// 读 -> 写模式,相当于 flip 的反向操作
public abstract ByteBuffer compact()
// 是否是间接内存
public abstract boolean isDirect()
- ByteBuffer bf = ByteBuffer.allocate(10);`,创立大小为 10 的 ByteBuffer 对象
- 写入数据
ByteBuffer buf ByteBuffer.allocate(10);
buf.put("csc".getBytes());
- 调用 flip 转换缓冲区为读模式;
buf.flip();
- 读取缓冲区中到内容:get();
System.out.println((char) buf.get());
零拷贝之 MappedByteBuffer
- 共享内存映射文件,对应的 ByteBuffer 子操作类,MappedByteBuffer 是基于 mmap 实现的。对于零拷贝的 mmap 的底层原理能够看看: 框架篇:小白也能秒懂的 Linux 零拷贝原理。MappedByteBuffer 须要 FileChannel 调用本地 map 函数映射。C++ 代码能够查阅下 FileChannelImpl.c-Java_sun_nio_ch_FileChannelImpl_map0 办法
- 应用 MappedByteBuffer 和文件映射,其读写能够缩小内存拷贝次数
FileChannel readChannel = FileChannel.open(Paths.get("./cscw.txt"), StandardOpenOption.READ);
MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 1024 * 40);
DirectByteBuffer 堆外内存回收机制 Cleaner
- 上面咱们看看间接内存的回收机制(java8);DirectByteBuffer 外部存在一个 Cleaner 对象,并且委托外部类 Deallocator 对象进行内存回收
class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer
{
// 构造函数
DirectByteBuffer(int cap) {
.... // 内存调配
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
...
}
private static class Deallocator implements Runnable{
...
public void run() {if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address); // 回收内存
address = 0;
Bits.unreserveMemory(size, capacity);
}
}
- 细看下 Cleaner,继承于 PhantomReference,并且在
public void clean()
办法会调用 Deallocator 进行革除操作
public class Cleaner extends PhantomReference<Object> {
// 如果 DirectByteBuffer 对象被回收,相应的 Cleaner 会被放入 dummyQueue 队列
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
// 构造函数
public static Cleaner create(Object var0, Runnable var1) {return var1 == null ? null : add(new Cleaner(var0, var1));
}
private Cleaner(Object var1, Runnable var2) {super(var1, dummyQueue);
this.thunk = var2;
}
private final Runnable thunk;
public void clean() {if (remove(this)) {
try {this.thunk.run();
} catch (final Throwable var2) {....
- 在 Reference 外部存在一个守护线程,循环获取 Reference,并判断是否 Cleaner 对象,如果是则调用其 clean 办法
public abstract class Reference<T>
static {ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg; tgn != null; g = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
...
handler.setDaemon(true);
handler.start();
...
}
...
// 外部类调用 tryHandlePending
private static class ReferenceHandler extends Thread {public void run() {while (true) {tryHandlePending(true);
}
}
...
static boolean tryHandlePending(boolean waitForNotify) {
Cleaner c;
.... // 从链表获取对象被回收的援用
// 判断 Reference 是否 Cleaner,如果是则调用其 clean 办法
if (c != null) {c.clean(); // 调用 Cleaner 的 clean 办法
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
netty 之 ByteBuf
- ByteBuf 原理
- Bytebuf 通过两个地位指针来帮助缓冲区的读写操作,别离是 readIndex 和 writerIndex
* +-------------------+------------------+------------------+
* | discardable bytes | readable bytes | writable bytes |
* | | (CONTENT) | |
* +-------------------+------------------+------------------+
* | | | |
* 0 <= readerIndex <= writerIndex <= capacity
- ByteBuf.API
// 获取 ByteBuf 分配器
public abstract ByteBufAllocator alloc()
// 抛弃可读字节
public abstract ByteBuf discardReadBytes()
// 返回读指针
public abstract int readerIndex()
// 设置读指针
public abstract ByteBuf readerIndex(int readerIndex);
// 标记以后读指针地位,配合 resetReaderIndex 应用
public abstract ByteBuf markReaderIndex()
public abstract ByteBuf resetReaderIndex()
// 返回可读字节数
public abstract int readableBytes()
// 返回写指针
public abstract int writerIndex()
// 设置写指针
public abstract ByteBuf writerIndex(int writerIndex);
// 标记以后写指针地位,配合 resetWriterIndex 应用
public abstract ByteBuf markWriterIndex()
public abstract ByteBuf resetWriterIndex()
// 返回可写字节数
public abstract int writableBytes()
public abstract ByteBuf clear();
// 设置读写指针
public abstract ByteBuf setIndex(int readerIndex, int writerIndex)
// 指针跳过 length
public abstract ByteBuf skipBytes(int length)
// 以以后地位切分 ByteBuf todo
public abstract ByteBuf slice();
// 切割起始地位为 index,长度为 length 的 ByteBuf todo
public abstract ByteBuf slice(int index, int length);
//Returns a copy of this buffer's readable bytes. // 复制 ByteBuf todo
public abstract ByteBuf copy()
// 是否可读
public abstract boolean isReadable()
// 是否可写
public abstract boolean isWritable()
// 字节编码程序
public abstract ByteOrder order()
// 是否在间接内存申请的 ByteBuf
public abstract boolean isDirect()
// 转为 jdk.NIO 的 ByteBuffer 类
public abstract ByteBuffer nioBuffer()
- 应用示例
public static void main(String[] args) {
// 调配大小为 10 的内存
ByteBuf buf = Unpooled.buffer(10);
// 写入
buf.writeBytes("csc".getBytes());
// 读取
byte[] b = new byte[3];
buf.readBytes(b);
System.out.println(new String(b));
System.out.println(buf.writerIndex());
System.out.println(buf.readerIndex());
}
----result----
csc
3
3
- ByteBuf 初始化时,readIndex 和 writerIndex 等于 0,调用
writeXXX() 办法
写入数据,writerIndex 会减少 (setXXX 办法无作用);调用readXXX() 办法
读取数据,则会使 readIndex 减少(getXXX 办法无作用),但不会超过 writerIndex - 在读取数据之后,0-readIndex 之间的 byte 数据被视为 discard,调用 discardReadBytes(),开释这部分空间,作用相似于 ByteBuffer 的 compact 办法
参考文章
- java.nio.ByteBuffer 用法小结
- Netty 系列 - 一分钟理解 ByteBuffer 和 ByteBuf 构造
- Netty 之无效躲避内存透露
正文完