Netty基础系列(4) --堆外内存与零拷贝详解
前言
到目前为止,我们知道Nio当中有三个最最核心的组件,分别是:Selelctor,Channel,Buffer。在Netty基础系列(3) --彻底理解NIO 这一篇文章中只是进行了大致的介绍。
我们现在来深入理解一下Buffer在 堆内创建内存 和 堆外创建内存 的底层原理,与 零拷贝 的具体实现。
Buffer
Buffer是一个抽象类,首先我们来看看Buffer有哪些实现类。

我们从上面这张截图可以看出,Buffer的直接子类有7种。除了Java中Boolean类型。剩余的7种基本类型都有与之对应的Buffer。不同类型的Buffer存储的内容也不同,比如说ByteBuffer存储的就是byte。IntBuffer存储的就是int。不要想得太复杂,把底层想象成数组即可。
接下来我们着重对ByteBuffer来进行讲解。理解了一个其他的理解起来都差不多。
首先我们来看ByteBuffer的继承关系图
D1OP_UQ36%5BJ$I.png)
由上面的继承关系图可以看出,ByteBuffer的子类有五个,分别为:
HeapByteBuffer:代表的是jvm堆内的缓存。
HeapByteBufferR: 代表的是jvm堆内的只读缓存。
MappedByteBuffer: 直接缓存的抽象基类。
DirectByteBuffer: 代表的是操作系统内存的缓存。
DirectByteBufferR: 代表的是操作系统内存的只读缓存
上面这几个类看名字和我的介绍我想你应该知道有什么区别了,这里其实只分为两大类。
分配在堆内存的缓存和分配在操作系统内存的缓存。
HeapByteBuffer
我们首先来看在堆内分配缓存的底层原理。
先来看一段代码。
public static void main(String args[]){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
}
我们直接调用ByteBuffer的静态方法创建了一个1024个字节的ByteBuffer缓存。那么ByteBuffer的静态方法allocate()在底层到底做了些什么呢?
我们再来看看ByteBuffer类对于静态方法allocate()的实现。
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>
{
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
}
没错,就是很简单。直接new了一个HeapByteBuffer对象,并指定大小为1024个字节。这里暂时不用管capacity是什么,后面我们会详细的讲解,在这里capacity就是我们传入的1024。
到目前为止,我们已经创建了一个HeapByteBuffer对象。我们创建这个对象的意义就是用来对Channel进行读写。此时我们内存模型已经变成了如下图所示:

对照着上图我们再来看看之前写的这个方法。
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
首先再栈空间的某个栈帧中创建了byteBuffer,接着将其指向堆内存中的对象HeapByteBuffer。
好了接下来是我们的重点!!!!
此时操作系统会自动在JVM之外的内存中分配一块内存空间,这部分内存空间的创建和销毁完全由操作系统来管理。我们无需在意。
Channel的数据无论是读还是写都是与操作系统分配的这块内存打交道而不是我们的堆内存,当准备读数据的时候,Channel将数据读到操作系统分配的内存中,然后再复制到JVM堆内存中的HeapByteBuffer对象中。写操作也是如此,当我们修改了HeapByteBuffer的数据,会将修改后的数据复制到操作系统分配的内存中,然后再写到Channel中。
我们之前学的普通的IO操作底层基本上都是如此,我们思考一下,为什么不能直接将Channel怼到HeapByteBuffer中呢?
没错,如果你有一定的开发经验,一定会想到垃圾回收器。当发送垃圾回收的时候,我们的对象在堆内存中是会发送移动的,移动后内存地址是会改变的,而io操作并不能追踪到你改变后的内存地址。所以只能在jvm外分配内存来操作数据。因为这一块内存从创建到销毁之间都是不会移动的。
DirectByteBuffer
我们来看看在堆外分配内存是如何实现的。
与前文一样,我们首先来看在操作系统中直接分配内存的底层原理。先来看一段代码。
public static void main(String args[]){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
}
与创建堆内缓存类似,我们直接调用ByteBuffer的静态方法创建了一个1024个字节的DirectByteBuffer缓存。那么ByteBuffer的静态方法allocateDirect()方法与allocate()方法又有什么区别呢?
我们再来看看ByteBuffer类对于静态方法allocateDirect()的实现。
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>
{
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
}
这里也是直接new了一个DirectByteBuffer对象,我们进入该对象的构造函数看看干了些什么

这里调用勒unsafe的allocateMemory(size)方法。我们进去后会发现这是一个native方法,底层调用的c语言的代码。就是在操作系统内存中分配了一个我们指定大小的内存用以操作数据。并且记录了这块内存的地址。
此时我们的内存模型如下图所示:

因为内存中这块内存不再是操作系统分配的,而是我们java代码调用native方法,自己分配的内存,并且记录了该内存的地址。所以我们操作数据就不需要再堆内操作可以直接在jvm内存以外的内存操作。此时每次读写操作都节省了两次内存复制操作。
这就是我们大名鼎鼎的zero copy(零拷贝)技术。
总结
其实我们多思考一下,这样的优势大吗?其实Channel中IO的操作相对于内存的复制来说是慢很多的,即便我们在读写数据的时候多了两次复制的过程对于整体来说影响是不大的。
那么什么时候就会体现出零拷贝的优势呢?有大量并发io操作,并且io操作是短暂完成的。这时由于节省了大量的内存copy操作,这些节省的时间积累下来也是非常可观的。
netty的底层就是用的零拷贝技术,所以netty能做到很好并发,之后我们会分析在netty中零拷贝是如何落实的。
Netty基础系列(4) --堆外内存与零拷贝详解的更多相关文章
- Java NIO 堆外内存与零拷贝
一.直接缓存 这个例子的区别就是 ByteBuffer.allocateDirect(512); 进入allocateDirect方法 进入DirectByteBuffer构造函数 Native方法: ...
- NIO堆外内存与零拷贝
重点: 1.0拷贝需要系统支持. 普通内存模型: java线程内存 --> 操作系统内存 --> 硬盘 直接内存模型: java --> 操作系统内存 --> 硬盘 两者对比, ...
- 深度学习基础系列(十一)| Keras中图像增强技术详解
在深度学习中,数据短缺是我们经常面临的一个问题,虽然现在有不少公开数据集,但跟大公司掌握的海量数据集相比,数量上仍然偏少,而某些特定领域的数据采集更是非常困难.根据之前的学习可知,数据量少带来的最直接 ...
- Netty基础系列(5) --零拷贝彻底分析
前言 上一节(堆外内存与零拷贝)当中我们从jvm堆内存的视角解释了一波零拷贝原理,但是仅仅这样还是不够的. 为了彻底搞懂零拷贝,我们趁热打铁,接着上一节来继续讲解零拷贝的底层原理. 感受一下NIO的速 ...
- Netty堆外内存泄漏排查,这一篇全讲清楚了
上篇文章介绍了Netty内存模型原理,由于Netty在使用不当会导致堆外内存泄漏,网上关于这方面的资料比较少,所以写下这篇文章,专门介绍排查Netty堆外内存相关的知识点,诊断工具,以及排查思路提供参 ...
- Netty之Java堆外内存扫盲贴
Java的堆外内存本来是高贵而神秘的东西,只在一些缓存方案的收费企业版里出现.但自从用了Netty,就变成了天天打交道的事情,毕竟堆外内存能减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接 ...
- Netty堆外内存泄露排查与总结
导读 Netty 是一个异步事件驱动的网络通信层框架,用于快速开发高可用高性能的服务端网络框架与客户端程序,它极大地简化了 TCP 和 UDP 套接字服务器等网络编程. Netty 底层基于 JDK ...
- Java堆外内存的使用
堆外内存的回收见HeapByteBuffer和DirectByteBuffer以及回收DirectByteBuffer 基本类型长度 在Java中有很多的基本类型,比如: byte,一个字节是8位bi ...
- JVM堆外内存随笔
一 JVM堆外内存 1)java与io(file,socket)的操作都需要堆外内存与jvm内存进行互相拷贝,因为操作系统是不懂jvm的内存结构的(jvm的内存结构是自管理的),所以堆外内存存放的是操 ...
随机推荐
- HDU 5510:Bazinga(暴力KMP)
http://acm.hdu.edu.cn/showproblem.php?pid=5510 Bazinga Problem Description Ladies and gentlemen, p ...
- 字符串匹配Boyer-Moore算法:文本编辑器中的查找功能是如何实现的?---这应该讲的最容易懂的文章了!
关于字符串匹配算法有很多,之前我有讲过一篇 KMP 匹配算法:图解字符串匹配 KMP 算法,不懂 kmp 的建议看下,写的还不错,这个算法虽然很牛逼,但在实际中用的并不是特别多.至于选择哪一种字符串匹 ...
- iOS组件化开发一pod库包含MRC的文件处理(五)
在做项目的过程中,建立了一个私有pod库,在这个库中存在mrc类文件这个时候如果在使用了arc的工程中引用这个pod的工程中手动设置当然也可以就是费时费力.现在我们来看看如何在私有库配置文件里配置自动 ...
- 微信小程序 CSS border-radius元素 overflow:hidden失效问题 iPhone ios 苹果兼容问题 伪类元素
同事找我解决一个问题 说安卓圆角没问题 苹果上失效了 我一看 其实就是没做兼容上图给你们看看 有没有看出来 其实就是父级设置圆角属性失效 父元素使用border-radius和overflow:hid ...
- Oracle数据库---触发器
SQL> --当我们对empnew执行删除操作之后,它就会出现一个提示信息,提示:这是删除操作!SQL> CREATE TRIGGER first_trigger 2 AFTER DELE ...
- c++学习书籍推荐《C++ Primer Plus中文版(第6版)》下载
百度云及其他网盘下载地址:点我 编辑推荐 一本经久不衰的C++畅销经典教程:一本支持C++11新标准的程序设计图书. 它被誉为“开发人员学习C++的教程,没有之一”! Amazon网站“Langua ...
- [ERROR]:INST-07008: Oracle 主目录(O) 位置的验证失败。用户没有创建主目录/实例位置的权限
安装weblogic12.1.3.0时,输入的安装命令是: 老是报这个错误. 百度半天好像没人报过这错……看来只有我这么粗心了…… 后来发现wls.rsp里面的Oracle_HOME指向目录错误,修改 ...
- python基础练习 dict切片
利用切片操作,实现一个trim()函数,去除字符串首尾的空格 def trim(s): if s[:1] != ' ' and s[-1:] != ' ': return s elif s[:1] = ...
- python 中多个装饰器的执行顺序
python 中多个装饰器的执行顺序: def wrapper1(f1): print('in wrapper1') def inner1(*args,**kwargs): print('in inn ...
- java练习---3
//程序员:罗元昊 2017.9.6public class World{ public static void main(String[] args){ double p=3.14,i=5.50; ...