热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【JAVA网络编程系列】NIOByteBuffer堆内与堆外内存

【JAVA网络编程系列】NIO--ByteBuffer堆内与堆外内存【1】Unsafe与堆外内存分配Unsafe操作直接内存的方法分配内存publicnativelonga

【JAVA 网络编程系列】NIO -- ByteBuffer 堆内与堆外内存

【1】Unsafe 与堆外内存分配

Unsafe 操作直接内存的方法

// 分配内存
public native long allocateMemory(long var1);
// 释放内存
public native void freeMemory(long var1);
// 设置内存值
public native void setMemory(Object var1, long var2, long var4, byte var6);
// 设置某种类型的值,比如putInt()
public native void putXxx(long var1, xxx var3);
// 获取某种类型的值,比如getInt()
public native xxx getXxx(long var1);

【2】ByteBuffer 的继承结构图

ByteBuffer 中分配堆内内存和堆外内存的方法

// 创建一个直接内存实现的ByteBuffer
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);
}// 创建一个堆内存实现的ByteBuffer
public static ByteBuffer allocate(int capacity) {if (capacity <0)throw new IllegalArgumentException();return new HeapByteBuffer(capacity, capacity);
}

【3】堆内内存 -- HeapByteBuffer

【3.1】HeapByteBuffer 使用示例

public class ByteBufferTest {public static void main(String[] args) {// 1. 创建一个堆内存实现的ByteBufferByteBuffer buffer &#61; ByteBuffer.allocate(12);// 2. 写入值buffer.putInt(1);buffer.putInt(2);buffer.putInt(3);// 3. 切换为读模式buffer.flip();// 4. 读取值System.out.println(buffer.getInt());System.out.println(buffer.getInt());System.out.println(buffer.getInt());}
}

【3.2】HeapByteBuffer -- allocate 的执行流程

// 1. 创建堆内存实现的ByteBuffer
public static ByteBuffer allocate(int capacity) {if (capacity <0)throw new IllegalArgumentException();return new HeapByteBuffer(capacity, capacity);
}
// 2. HeapByteBuffer的构造方法
HeapByteBuffer(int cap, int lim) {// lim &#61; cap &#61; 12// 创建了一个12大小的byte数组// 调用父构造方法super(-1, 0, lim, cap, new byte[cap], 0);
}
// 3. ByteBuffer的构造方法
ByteBuffer(int mark, int pos, int lim, int cap,byte[] hb, int offset)
{// 调用父构造方法// pos &#61; 0&#xff0c;默认创建的就是写模式// lim &#61; cap &#61; 12super(mark, pos, lim, cap);// byte数组hb&#xff08;heap buffer&#xff09;&#xff0c;为上面传过来的new byte[cap]this.hb &#61; hb;this.offset &#61; offset;
}
// 4. Buffer的构造方法
Buffer(int mark, int pos, int lim, int cap) {if (cap <0)throw new IllegalArgumentException("Negative capacity: " &#43; cap);// 三个非常重要的变量&#xff1a;capacity、limit、positionthis.capacity &#61; cap;limit(lim);position(pos);if (mark >&#61; 0) {if (mark > pos)throw new IllegalArgumentException("mark > position: ("&#43; mark &#43; " > " &#43; pos &#43; ")");this.mark &#61; mark;}
}/*** 设置此缓冲区的限制* 若 position 大于 newLimit 则将 position 设置为 newLimit* 若 mark 已定义且大于 newLimit 则丢弃该 mark*/
public final Buffer limit(int newLimit) {if ((newLimit > capacity) || (newLimit <0))throw new IllegalArgumentException();limit &#61; newLimit;if (position > limit) position &#61; limit;if (mark > limit) mark &#61; -1;return this;
}/*** 设置此缓冲区的位置* 若 mark 已定义且大于 newPosition 则丢弃该 mark*/
public final Buffer position(int newPosition) {if ((newPosition > limit) || (newPosition <0))throw new IllegalArgumentException();position &#61; newPosition;if (mark > position) mark &#61; -1;return this;
}

【3.3】HeapByteBuffer -- putInt 的执行流程

// 写入一个int类型的数值
public ByteBuffer putInt(int x) {// 调用Bits工具类的putInt()方法&#xff0c;Bits是位的意思// 堆内存的实现中使用大端法来存储数据Bits.putInt(this, ix(nextPutIndex(4)), x, bigEndian);return this;
}
// 移动position到下一个位置
// 因为一个int占4个字节&#xff0c;所以这里往后移动4位
final int nextPutIndex(int nb) {// 判断有没有越界if (limit - position }
// 计算写入的偏移量&#xff0c;初始值为0
protected int ix(int i) {return i &#43; offset;
}
// java.nio.Bits#putInt(java.nio.ByteBuffer, int, int, boolean)
static void putInt(ByteBuffer bb, int bi, int x, boolean bigEndian) {// 堆内存使用的是大端法if (bigEndian)// 大端法putIntB(bb, bi, x);else// 小端法putIntL(bb, bi, x);
}
// java.nio.Bits#putIntB(java.nio.ByteBuffer, int, int)
static void putIntB(ByteBuffer bb, int bi, int x) {// 把一个int拆分成4个byte&#xff0c;分别写入// int3(int x) { return (byte)(x >> 24); }bb._put(bi , int3(x));// int2(int x) { return (byte)(x >> 16); }bb._put(bi &#43; 1, int2(x));// int1(int x) { return (byte)(x >> 8); }bb._put(bi &#43; 2, int1(x));// int0(int x) { return (byte)(x ); }bb._put(bi &#43; 3, int0(x));
}
// java.nio.HeapByteBuffer#_put
void _put(int i, byte b) {// 最终变成了修改byte数组hb[i] &#61; b;
}

【3.4】HeapByteBuffer -- flip() 的执行流程

/*** 反转此缓冲区* 1. 将限制设置为当前位置* 2. 将位置设置为 0* 3. 若定义了标记则丢弃该标记* 效果 缩小 limit 的范围* 使用场景 向缓冲区存储数据&#xff0c;然后再从缓冲区读取这些数据时调用*/
public final Buffer flip() {limit &#61; position;position &#61; 0;mark &#61; -1;return this;
}

【3.5】HeapByteBuffer -- getInt() 的执行流程

java.nio.HeapByteBuffer
public int getInt() {// 调用Bits类的getInt方法return Bits.getInt(this, ix(nextGetIndex(4)), bigEndian);
}
// 计算偏移量
protected int ix(int i) {return i &#43; offset;
}
// 返回byte数组索引i处的值
byte _get(int i) {return hb[i];
}java.nio.Buffer
final int nextGetIndex(int nb) {if (limit - position }java.nio.Bits
// 根据大小端构造int数据
static int getInt(ByteBuffer bb, int bi, boolean bigEndian) {// 此处使用大端return bigEndian ? getIntB(bb, bi) : getIntL(bb, bi) ;
}// 按大端模式获取int数值
static int getIntB(ByteBuffer bb, int bi) {return makeInt(bb._get(bi ),bb._get(bi &#43; 1),bb._get(bi &#43; 2),bb._get(bi &#43; 3));
}static private int makeInt(byte b3, byte b2, byte b1, byte b0) {// 根据byte的值构造int数值return (((b3 ) <<24) |((b2 & 0xff) <<16) |((b1 & 0xff) <<8) |((b0 & 0xff) ));
}

【4】堆外内存 -- DirectByteBuffer

【4.1】DirectByteBuffer 使用示例

public class ByteBufferTest {public static void main(String[] args) {// 创建一个直接内存实现的ByteBufferByteBuffer buffer &#61; ByteBuffer.allocateDirect(12);// 写入值buffer.putInt(1);buffer.putInt(2);buffer.putInt(3);// 切换为读模式buffer.flip();// 读取值System.out.println(buffer.getInt());System.out.println(buffer.getInt());System.out.println(buffer.getInt());}
}

【4.2】DirectByteBuffer -- allocate 的执行流程

public static ByteBuffer allocateDirect(int capacity) {// 创建直接内存实现的ByteBufferreturn new DirectByteBuffer(capacity);
}DirectByteBuffer(int cap) {// 调用父构造方法&#xff0c;设置position/limit/capacity/mark这几个值// 与HeapByteBuffer类似&#xff0c;只不过没有创建hb那个数组super(-1, 0, cap, cap);// 是否页对齐&#xff0c;默认为否boolean pa &#61; VM.isDirectMemoryPageAligned();// 每页大小int ps &#61; Bits.pageSize();long size &#61; Math.max(1L, (long)cap &#43; (pa ? ps : 0));// 先预订内存&#xff0c;如果内存不够&#xff0c;会进行清理&#xff0c;并尝试几次Bits.reserveMemory(size, cap);long base &#61; 0;try {// 调用unsafe的allocateMemory()方法来分配内存base &#61; unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {// 释放预定的内存Bits.unreserveMemory(size, cap);throw x;}// 初始化这片内存的值为0unsafe.setMemory(base, size, (byte) 0);// 根据是否页对齐计算实际的地址if (pa && (base % ps !&#61; 0)) {// address为Buffer类中的long类型变量address &#61; base &#43; ps - (base & (ps - 1));} else {// 默认不页对齐&#xff0c;地址取allocateMemory()返回的地址address &#61; base;}// 创建cleaner用于释放内存cleaner &#61; Cleaner.create(this, new Deallocator(base, size, cap));att &#61; null;
}

【4.3】DirectByteBuffer -- getInt/putInt 的执行流程

java.nio.DirectByteBuffer
protected static final boolean unaligned &#61; Bits.unaligned();public ByteBuffer putInt(int x) {// 1 <<2 &#61; 4&#xff0c;一个int占4个字节putInt(ix(nextPutIndex((1 <<2))), x);return this;
}
private ByteBuffer putInt(long a, int x) {if (unaligned) {int y &#61; (x);// 对齐的情况下&#xff0c;直接调用unsafe.putIntunsafe.putInt(a, (nativeByteOrder ? y : Bits.swap(y)));} else {// 否则调用Bits.putIntBits.putInt(a, x, bigEndian);}return this;
}public int getInt() {// 1 <<2 &#61; 4&#xff0c;一个int占4个字节return getInt(ix(nextGetIndex((1 <<2))));
}
private int getInt(long a) {if (unaligned) {int x &#61; unsafe.getInt(a);return (nativeByteOrder ? x : Bits.swap(x));}return Bits.getInt(a, bigEndian);
}// 计算偏移量&#xff0c;在address的基础上加上position的值
private long ix(int i) {return address &#43; (i <<0);
}java.nio.Buffer
final int nextPutIndex(int nb) {if (limit - position }java.nio.ByteBuffer
boolean nativeByteOrder &#61; (Bits.byteOrder() &#61;&#61; ByteOrder.BIG_ENDIAN);java.nio.Bits
static int getInt(long a, boolean bigEndian) {return bigEndian ? getIntB(a) : getIntL(a) ;
}static int getIntB(long a) {return makeInt(_get(a ),_get(a &#43; 1),_get(a &#43; 2),_get(a &#43; 3));
}private static byte _get(long a) {return unsafe.getByte(a);
}static private int makeInt(byte b3, byte b2, byte b1, byte b0) {return (((b3 ) <<24) |((b2 & 0xff) <<16) |((b1 & 0xff) <<8) |((b0 & 0xff) ));
}static void putInt(long a, int x, boolean bigEndian) {if (bigEndian)putIntB(a, x);elseputIntL(a, x);
}static void putIntB(long a, int x) {// 把一个int拆分成4个byte&#xff0c;分别写入// int3(int x) { return (byte)(x >> 24); }_put(a , int3(x));// int2(int x) { return (byte)(x >> 16); }_put(a &#43; 1, int2(x));// int1(int x) { return (byte)(x >> 8); }_put(a &#43; 2, int1(x));// int0(int x) { return (byte)(x ); }_put(a &#43; 3, int0(x));
}private static void _put(long a, byte b) {unsafe.putByte(a, b);
}

【4.4】DirectByteBuffer -- 内存释放

【4.4.1】关键类 -- Deallocator/Cleaner

java.nio.DirectByteBuffer
private static class Deallocatorimplements Runnable
{private static Unsafe unsafe &#61; Unsafe.getUnsafe();private long address;private long size;private int capacity;// 构造方法传入allocate的时候返回的地址&#xff0c;以及容量等参数private Deallocator(long address, long size, int capacity) {assert (address !&#61; 0);this.address &#61; address;this.size &#61; size;this.capacity &#61; capacity;}public void run() {if (address &#61;&#61; 0) {return;}// 调用unsafe的freeMemory释放内存unsafe.freeMemory(address);address &#61; 0;// 取消预订的内存Bits.unreserveMemory(size, capacity);}
}

public class Cleaner extends PhantomReference {private static final ReferenceQueue dummyQueue &#61; new ReferenceQueue();// Cleaner 内部有一个链表private static Cleaner first &#61; null;private Cleaner next &#61; null;private Cleaner prev &#61; null;// Runnable 方法private final Runnable thunk;// 将var0添加到Cleaner链表的前端private static synchronized Cleaner add(Cleaner var0) {if (first !&#61; null) {var0.next &#61; first;first.prev &#61; var0;}first &#61; var0;return var0;}// 将var0从链表中移除private static synchronized boolean remove(Cleaner var0) {if (var0.next &#61;&#61; var0) {return false;} else {if (first &#61;&#61; var0) {if (var0.next !&#61; null) {first &#61; var0.next;} else {first &#61; var0.prev;}}if (var0.next !&#61; null) {var0.next.prev &#61; var0.prev;}if (var0.prev !&#61; null) {var0.prev.next &#61; var0.next;}var0.next &#61; var0;var0.prev &#61; var0;return true;}}private Cleaner(Object var1, Runnable var2) {// 调用父类的构造方法// Cleaner这个虚引用引用的对象是var1super(var1, dummyQueue);// var2即Deallocator对象this.thunk &#61; var2;}public static Cleaner create(Object var0, Runnable var1) {// 创建一个Cleaner对象&#xff0c;并返回这个对象// 它里面封装了一个任务return var1 &#61;&#61; null ? null : add(new Cleaner(var0, var1));}public void clean() {// 从链表中移除当前对象if (remove(this)) {try {// 执行任务this.thunk.run();} catch (final Throwable var2) {// 处理异常AccessController.doPrivileged(new PrivilegedAction() {public Void run() {if (System.err !&#61; null) {(new Error("Cleaner terminated abnormally", var2)).printStackTrace();}System.exit(1);return null;}});}}}
}

【4.4.2】内存释放流程简介

1. DirectByteBuffer 本身是一个堆内存中的对象&#xff0c;其中具有 address 属性用于保存直接内存的地址&#xff0c;操作 DirectByteBuffer 实际上是通过 unsafe 对 address 指向地址的操作&#xff1b;
2. Cleaner 是一个虚引用&#xff0c;引用的对象是 DirectByteBuffer&#xff0c;并通过 Cleaner.create(this, new Deallocator(base, size, cap)) 注册 Deallocator 任务&#xff1b;
3. 当 DirectByteBuffer 不具有强引用时&#xff0c;随时都可能被 gc 从堆内存清理掉&#xff0c;此时&#xff0c;JVM 会把上面绑定的 Cleaner 对象放到 Reference 的 discovered 队列中&#xff1b;
4. Reference 中的线程 ReferenceHandler 不断轮循&#xff0c;把 discovered 队列中的虚引用赋值到 pending 队列中&#xff0c;并且若该虚引用是 Cleaner 对象&#xff0c;则执行其 clean() 方法&#xff0c;且会把该虚引用加入到 ReferenceQueue 队列中&#xff1b;
5. 执行 clean() 方法的时候将会执行 Deallocator 的 run() 方法&#xff0c;在这里调用 unsafe 的 freeMemory() 清理掉直接内存&#xff1b;

参考致谢

本博客为博主学习笔记&#xff0c;同时参考了网上众博主的博文以及相关专业书籍&#xff0c;在此表示感谢&#xff0c;本文若存在不足之处&#xff0c;请批评指正。

【1】慕课专栏&#xff0c;网络编程之Netty一站式精讲

【2】极客时间&#xff0c;Netty源码剖析与实战

【3】死磕 java魔法类之Unsafe解析


推荐阅读
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 生产环境下JVM调优参数的设置实例
     正文前先来一波福利推荐: 福利一:百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。福利二 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • Question该提问来源于开源项目:react-native-device-info/react-native-device-info ... [详细]
  • 这个问题困扰了我两天,卸载Dr.COM客户端(我们学校上网要装这个客户端登陆服务器,以后只能在网页里输入用户名和密码了),问题解决了。问题的现象:在实验室机台式机上安装openfire和sp ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • 近来有一个需求,是需要在androidjava基础库中插入一些log信息,完成这个工作需要的前置条件有编译好的android源码具体android源码如何编译,这 ... [详细]
  • 1.webkit内核中的一些私有的meta标签,这些meta标签在开发webapp时起到非常重要的作用(1) ... [详细]
author-avatar
狄言洁_171
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有