ByteBuffer

1、堆内:HeapByteBuffer,在java的堆内创建。

缺点:可能引起堆的不断gc

写文件的时候需要先将堆的buffer写进直接buffer里,然后再写入文件

2、堆外:DirectByteBuffer  在堆外创建

优点:写文件不像HeapByteBuffer那样需要先写到直接buffer再写文件。

不会引起频繁的堆gc

缺点:在full gc回收堆外内存的时候可能如果超时可能引起OOM

堆外Buffer的回收是在full gc的时候回收的

3、Netty采取的池化ByteBuffer,性能提高了23倍

http://calvin1978.blogcn.com/articles/directbytebuffer.html

Java的堆外内存本来是高贵而神秘的东西,只在一些缓存方案的收费企业版里出现。但自从用了Netty,就变成了天天打交道的事情,毕竟堆外内存能减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中;而且也没了烦人的GC。

好在,Netty所用的堆外内存只是Java NIO的 DirectByteBuffer类,通读一次很快。还有一些sun.misc.*的类木有源码,要自己跑去OpenJdk那看个明白。

1. 堆外内存的创建

在DirectByteBuffer中,首先向Bits类申请额度,Bits类有一个全局的 totalCapacity变量,记录着全部DirectByteBuffer的总大小,每次申请,都先看看是否超限 -- 堆外内存的限额默认与堆内内存(由-XMX 设定)相仿,可用 -XX:MaxDirectMemorySize 重新设定。

如果已经超限,会主动执行Sytem.gc(),期待能主动回收一点堆外内存。然后休眠一百毫秒,看看totalCapacity降下来没有,如果内存还是不足,就抛出大家最头痛的OOM异常。

如果额度被批准,就调用大名鼎鼎的sun.misc.Unsafe去分配内存,返回内存基地址,Unsafe的C++实现在此,标准的malloc。然后再调一次Unsafe把这段内存给清零。跑个题,Unsafe的名字是提醒大家这个类只给Sun自家用的,你们别用,不然哪天Sun把它藏起来了你们就哭死。果然,JDK9里就Oracle可能动手哦

JDK7开始,DirectByteBuffer分配内存时默认已不做分页对齐,不会再每次分配并清零 实际需要+分页大小(4k)的内存,这对性能应有较大提升,所以Oracle专门写在了Enhancements in Java I/O里。

最后,创建一个Cleaner,并把代表清理动作的Deallocator类绑定 -- 降低Bits里的totalCapacity,并调用Unsafe调free去释放内存。Cleaner的触发机制后面再说。

2. 堆外内存基于GC的回收

存在于堆内的DirectByteBuffer对象很小,只存着基地址和大小等几个属性,和一个Cleaner,但它代表着后面所分配的一大段内存,是所谓的冰山对象。通过前面说的Cleaner,堆内的DirectByteBuffer对象被GC时,它背后的堆外内存也会被回收。

快速回顾一下堆内的GC机制,当新生代满了,就会发生young gc;如果此时对象还没失效,就不会被回收;撑过几次young gc后,对象被迁移到老生代;当老生代也满了,就会发生full gc。

这里可以看到一种尴尬的情况,因为DirectByteBuffer本身的个头很小,只要熬过了young gc,即使已经失效了也能在老生代里舒服的呆着,不容易把老生代撑爆触发full gc,如果没有别的大块头进入老生代触发full gc,就一直在那耗着,占着一大片堆外内存不释放。

这时,就只能靠前面提到的申请额度超限时触发的system.gc()来救场了。但这道最后的保险其实也不很好,首先它会中断整个进程,然后它让当前线程睡了整整一百毫秒,而且如果gc没在一百毫秒内完成,它仍然会无情的抛出OOM异常。还有,万一,万一大家迷信某个调优指南设置了-DisableExplicitGC禁止了system.gc(),那就不好玩了。

所以,堆外内存还是自己主动点回收更好,比如Netty就是这么做的。

3. 堆外内存的主动回收

对于Sun的JDK这其实很简单,只要从DirectByteBuffer里取出那个sun.misc.Cleaner,然后调用它的clean()就行。

前面说的,clean()执行时实际调用的是被绑定的Deallocator类,这个类可被重复执行,释放过了就不再释放。所以GC时再被动执行一次clean()也没所谓。

在Netty里,因为不确定跑在Sun的JDK里(比如安卓),所以多废了些功夫来确定Cleaner的存在。

4. Cleaner如何与GC相关联?

涨知识的时间到了,原来JDK除了StrongReference,SoftReference 和 WeakReference之外,还有一种PhantomReference,Phantom是幻影的意思,Cleaner就是PhantomReference的子类。

当GC时发现它除了PhantomReference外已不可达(持有它的DirectByteBuffer失效了),就会把它放进 Reference类pending list静态变量里。然后另有一条ReferenceHandler线程,名字叫 "Reference Handler"的,关注着这个pending list,如果看到有对象类型是Cleaner,就会执行它的clean(),其他类型就放入应用构造Reference时传入的ReferenceQueue中,这样应用的代码可以从Queue里拖出这些理论上已死的对象,做爱做的事情——这是一种比finalizer更轻量更好的机制。

5. 其实

专家们说,OpenJDK没有接受jemalloc(redis们在用)的补丁,直接用malloc在OS里申请一段内存,比在已申请好的JVM堆内内存里划一块出来要慢,所以我们在Netty一般用池化的 PooledDirectByteBuf 对DirectByteBuffer进行重用 ,《Netty权威指南》说性能提升了23倍,所以基本不需要头痛堆外内存的释放,顺便还告别了大数据流量下的频繁GC。

时间: 2024-10-27 10:05:46

ByteBuffer的相关文章

Android中直播视频技术探究之---基础核心类ByteBuffer解析

一.前言 前一篇文章我们介绍了Android中直播视频技术的基础大纲知识,这里就开始一一讲解各个知识点,首先主要来看一下视频直播中的一个重要的基础核心类:ByteBuffer,这个类看上去都知道了,是字节缓冲区处理字节的,这个类的功能非常强大,也在各个场景都有用到,比如网络数据底层处理,特别是结合网络通道信息处理的时候,还有就是后面要说到的OpenGL技术也要用到,当然在视频处理中也是很重要的,因为要处理视频流信息,比如在使用MediaCodec进行底层的视频流编码的时候,处理的就是字节,我们如

Direct ByteBuffer学习

ByteBuffer有两种一种是heap ByteBuffer,该类对象分配在JVM的堆内存里面,直接由Java虚拟机负责垃圾回收,一种是direct ByteBuffer是通过jni在虚拟机外内存中分配的.通过jmap无法查看该快内存的使用情况.只能通过top来看它的内存使用情况. JVM堆内存大小可以通过-Xmx来设置,同样的direct ByteBuffer可以通过-XX:MaxDirectMemorySize来设置,此参数的含义是当Direct ByteBuffer分配的堆外内存到达指定

NIO byteBUffer 讲解 及Mina 源码分析

1.传统的socket: 阻塞式通信模式 tcp连接: 与服务器连接时 .必须等到连接成功后 才返回 . udp连接: 客户端发送数据 ,必须等到发送成功后返回 . 每建立一个 Scoket连接时, 同事创建一个新线程对该 Socket进行单独通信(采用阻塞式通信 ) 这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较少的时候非常有效,但是如果 对每一个连接都产生一个线程的无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况 2.1NIO 设计背后的基石:反应器模式,用于事

ByteBuffer的介绍

转摘 有一个问题需要明确:为什么要使用bytebuffer,它比byte比起来有什么优点? 很简单:为了提高IO的效率.怎样提高的,这个还得google一下. 记住几个标志的含义:position:当前指针的位置,也就是接下来要读写的位置.limit:限制,一个缓冲区可读写的范围.capability:容量,一个缓冲区最多的存放的字节数.mark:标志位,记录当前的位置. 界限是用来控制当前读写的范围,如果容量为100,界限为10,则位置只能在0-10之间,即只能读写0-10之间的数据. 几个操

今天学到FileChannel 和 ByteBuffer 出现了NonWritableChannelException

查了半小时的异常,最后发现实际类型居然是输入流,而不是输出流 仅此日记警告自己一定不能心急,晚上累了就好好休息吧.... ByteBuffer.flip();必须在进行读取该ByteBuffer之前调用这个方法,告知ByteBuffer准备数据. ByteBuffer.clear();在读取里面的数据之后,如若想要再次往该ByteBuffer里填充数据,则调用此方法告知准备写入.

ByteBuffer常用方法详解

缓冲区(Buffer) 缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I/O)的数据作临时存储,这部分预留的内存空间就叫做缓冲区: 使用缓冲区有这么两个好处: 1.减少实际的物理读写次数 2.缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数 举个简单的例子,比如A地有1w块砖要搬到B地 由于没有工具(缓冲区),我们一次只能搬一本,那么就要搬1w次(实际读写次数) 如果A,B两地距离很远的话(IO性能消耗),那么性能消耗将会很大 但是

ByteBuffer源码分析

在进行数据传输的时候,往往需要使用到缓冲区,常用的缓冲区就是JDK NIO类库中提供的java.nio.Buffer,实现类如下: 在使用NIO编程时,最常用的是其中的ByteBuffer,本篇分析ByteBuffer内部的源码实现,顺序从父类Buffer入手,了解父类中基础API的实现,再到各个实现子类的实现. Buffer Buffer是存放一种特定的.原始的数据的容器.Buffer是一种特定原始类型元素的线性的有限序列集合,其核心的属性有capacity.limit.Position. c

今天使用JDK1.8中的ByteBuffer 发现其中基本数据必须要是偶数个的

private static void symmertricScramble(CharBuffer cb) { while (cb.hasRemaining()) { cb.mark(); char c1 = cb.get(); char c2 = cb.get(); cb.reset(); cb.put(c2).put(c1); } } public static void main(String[] args) throws Exception { //如果这里是奇数个的数据就会抛出Buff

ByteBuffer: 当由一个byte[]来生成一个固定不变的ByteBuffer时,使用ByteBuffer.wrap(byte[]);

StringBuilder sb = new StringBuilder(1024); //向sb中写入900个左右的随机字符内容 for(int j=1; j< 50;j++) { sb.append(Math.random()); } //System.out.println("sb:" + sb.length()); long startTime = System.currentTimeMillis(); for(int i=0;i<10000;i++) { /* b

收藏:Non-direct与direct ByteBuffer区别

相信大家都知道,但是两者的区别在什么地方呢?在不同的环境下采用哪种类型的ByteBuffer会更有效率呢?先解释一下两者的区别:Non-directByteBuffer内存是分配在堆上的,直接由Java虚拟机负责垃圾收集,你可以把它想象成一个字节数组的包装类,如下伪码所示:HeapByteBuffer extends ByteBuffer {    byte[] content;    int position, limit, capacity;    ......}而DirectByteBuf