NIO之缓冲区
NIO引入了三个概念:
- Buffer 缓冲区
- Channel 通道
- selector 选择器
1、java.io优化建议
操作系统与Java基于流的I/O模型有些不匹配。操作系统要移动的是大块数据(缓冲区),这往往是在硬件直接存储器存取(DMA)的协助下完成的。I/O类喜欢操作小块数据——单个字节、几行文本。结果,操作系统送来整缓冲区的数据,java.io的流数据类再花大量时间把它们拆成小块,往往拷贝一个小块就要往返于几层对象。操作系统喜欢整卡车地运来数据,java.io类则喜欢一铲子一铲子地加工数据。
—— 引自《JAVA NIO》
在传统的java.io中,面向单字节的读写效率是十分低下的,尤其说频繁的读写和操作大文件,效率相差可达千百倍。[注:这里说的是面向字节的读写效率底下,而不是说传统的io效率底下]
下面是一些优化建议:
- 尽量避免单字节读写,例如 IuputStream.read(byte b),OutputStream.write(byte b)
- 尽量使用基于数组API的读写数据,例如 IuputStream.read(byte[] b),OutputStream.write(byte[] b)
- 尽量使用基于缓冲的类进行读写,例如BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter
- 对文件随机操作读取和写入时,用RandomAccessFile类
- 用System.arrayCopy在数组间进行复制
2、java.nio中Buffer
从上面IO的优化建议中,我们可以看到很多Buffer的影子。基于缓冲的读写,大多数情况下可以提高IO效率。nio中Buffer的引入,使得java的IO模型更贴近操作系统底层,面向Buffer的读写操作更高效,同时也在API层面避免了单字节操作。
- 2.1 ByteBuffer字节缓冲区
操作系统的IO是以字节为单位的,因此,字节缓冲区跟其他缓冲区不同,对操作系统的IO只能是基于字节缓冲区的,所以通道(channel)只接收ByteBuffer作为参数。
- 2.2 直接缓冲区和非直接缓冲区
ByteBuffer又分为直接缓冲区和非直接缓冲区。
非直接缓冲区可以通过ByteBuffer.wrap(byte[] array);ByteBuffer.allocate(int capacity)这两个方法来创建
直接缓冲区可通过ByteBuffer.allocateDirect(int capacity)来创建
字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
对直接缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
直接字节缓冲区还可以通过映射将文件区域直接映射到内存中来创建。Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。
—— 引自《JDK API 1.6.0》
2.3 直接缓冲区跟非直接缓冲区的区别
JDK中的说明不太容易理解,我们从源码层面来分析二者的区别。
public abstract class Buffer {
............
// Used only by direct buffers
// NOTE: hoisted here for speed in JNI GetDirectBufferAddress
long address;
...........
7 }
在Buffer类中定义了一个变量adress,注释为仅作为直接缓冲区使用,通过调用JNI的方法来获得一个内存地址。
也就是说,直接缓冲区说指向内存中的某个地址,而不是JVM中的某个区域。由于是内存中的某个区域,并且通过JNI去调用操作系统底层的指令,因此在某些情况下相对的高效。非直接缓冲区指向的是JVM内某个数组空间。
再来看创建直接缓冲区的一些有趣细节
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap, false);
Bits.reserveMemory(cap);
int ps = Bits.pageSize();//1、获取内存分页大小
long base = 0;
try {
base = unsafe.allocateMemory(cap + ps);//2、分配内存空间,分配的空间比容量大,多出一个分页大小,为了后面调整起始位置也分页对齐
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(cap);
throw x;
}
unsafe.setMemory(base, cap + ps, (byte) 0);
if (base % ps != 0) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));//3、缓冲区起始地址与分页对齐,方便寻址
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, cap));
21 }
虽然直接缓冲区说独立于JVM外的一块区域,但是在创建的时候,可以通过设置JVM的启动参数来限制大小。
-XX:MaxDirectMemorySize=<size>
继续看Bits.reserveMemory(cap);,这个类并没有对内存进行实际的操纵,只是记录内存对应的一些参数信息。
static void reserveMemory(long size) {
synchronized (Bits.class) {
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
if (size <= maxMemory - reservedMemory) {//如果创建直接缓冲区后的内存占用不超过最大内存限制
reservedMemory += size;//更新已分配的内存大小
return;
}
}
//如果超过最大内存限制,执行垃圾回收
System.gc();
try {
Thread.sleep(100);//等待垃圾回收完成
} catch (InterruptedException x) {
// Restore interrupt status
Thread.currentThread().interrupt();
}
synchronized (Bits.class) {
if (reservedMemory + size > maxMemory)//如果依然超过最大内存限制,则抛出内存溢出异常
throw new OutOfMemoryError("Direct buffer memory");
reservedMemory += size;
}
- 2.4 非直接缓冲区的释放
由于DirectByteBuffer直接开辟一块内存当作缓冲区,并且调用操作系统的方法去读写,因此效率高。但是,也不盲目的去用DirectByteBuffer,如果使用不当,它也会带来一些问题,例如直接缓冲区独立于JVM之外,GC不能对这部分空间进行释放。
那么直接缓冲区是如何被释放的?来看源码
cleaner = Cleaner.create(this, new Deallocator(base, cap));
private static class Deallocator
implements Runnable
{
......
public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address);//通知操作系统释放对应的内存区域
address = 0;
Bits.unreserveMemory(capacity);//更新JVM参数
}
........
}
创建直接缓冲的时候,会创建一个Cleaner来,在Deallocator中释放对应的内存区域。但是cleaner没法显示调用,因此无法手动释放直接缓冲区。
在使用直接缓冲区的时候应该注意:只有等DirectByteBuffer对象被jvm垃圾回收时,才会给操作指令去释放对应的内存。由于垃圾回收具有不确定行,即使显示调用GC,也可能不进行垃圾回收,因此这部分区域可能无法及时释放。
这里提供一种手动释放的方法,用到了反射,仅用来交流,但是不推荐使用(破坏了原有的java规范),除非在必要的情况下。
public static void destroyDirectByteBuffer(ByteBuffer toBeDestroyed)
throws Exception {
if (!toBeDestroyed.isDirect()) {
return;
}
Method cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner");
cleanerMethod.setAccessible(true);
Object cleaner = cleanerMethod.invoke(toBeDestroyed);
Method cleanMethod = cleaner.getClass().getMethod("clean");
cleanMethod.setAccessible(true);
cleanMethod.invoke(cleaner);
}
NIO之缓冲区的更多相关文章
- NIO 之 缓冲区(Buffer)
缓存区是java nio的核心部分,所以必须熟悉它的一些操作. 实现类型: nio中实现了除布尔型(boolean)外的其他7种基本数据类型的buffer(ByteBuffer,CharBuffer, ...
- Java NIO 之缓冲区
缓冲区基础 所有的缓冲区都具有四个属性来 供关于其所包含的数据元素的信息. capacity(容量):缓冲区能够容纳数据的最大值,创建缓冲区后不能改变. limit(上界):缓冲区的第一个不能被读或写 ...
- Java NIO之缓冲区Buffer
Java NIO的核心部件: Buffer Channel Selector Buffer 是一个数组,但具有内部状态.如下4个索引: capacity:总容量 position:下一个要读取/写入的 ...
- Java NIO——2 缓冲区
一.缓冲区基础 1.缓冲区并不是多线程安全的. 2.属性(容量.上界.位置.标记) capacity limit 第一个不能被读或写的元素 position 下一个要被读或写的元素索引 mark ...
- Java NIO Buffer缓冲区
原文链接:http://tutorials.jenkov.com/java-nio/buffers.html Java NIO Buffers用于和NIO Channel交互.正如你已经知道的,我们从 ...
- NIO buffer 缓冲区 API
package bhz.nio.test; import java.nio.IntBuffer; public class TestBuffer { public static void main(S ...
- NIO之缓冲区(Buffer)的数据存取
缓冲区(Buffer) 一个用于特定基本数据类行的容器.有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类. Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道 ...
- NIO的缓冲区、通道、选择器关系理解
Buffer的数据存取 一个用于特定基本数据类行的容器.有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类. Java NIO中的Buffer主要用于与NIO通道进行交互,数 ...
- Java NIO ———— Buffer 缓冲区详解 入门
引言缓冲区是一个用于特定基本类型的容器.由java.nio 包定义,所有缓冲区都是 Buffer 抽象类的子类. Java NIO 中的 Buffer ,主要用于与NIO 通道进行交互.数据从通道存入 ...
随机推荐
- SolrCloud集群搭建(基于zookeeper)
1. 环境准备 1.1 三台Linux机器,x64系统 1.2 jdk1.8 1.3 Solr5.5 2. 安装zookeeper集群 2.1 分别在三台机器上创建目录 mkdir /usr/hdp/ ...
- [20180630]truncate table的另类恢复2.txt
[20180630]truncate table的另类恢复2.txt --//上个星期做了truncate table的另类恢复,通过修改数据块的段号,再通过rowid定位收集数据,达到修复的目的.- ...
- element-ui的回调函数Events的用法
做轮播的时候想用这个change回调函数,但是官方文档上竟然就只列了这么一行东西,完全没有示例代码(也可能我没找到哈) 鼓捣了半天,东拼西凑终于找到了靠谱的使用方法,其实很简单 在轮播组件上加上@ch ...
- Hive-1.2.1_05_案例操作
1. 建库建表 # 建库 create database exercise; # 建表 create table student(Sno int,Sname string,Sex string,Sag ...
- Hadoop优化 第一篇 : HDFS/MapReduce
比较惭愧,博客很久(半年)没更新了.最近也自己搭了个博客,wordpress玩的还不是很熟,感兴趣的朋友可以多多交流哈!地址是:http://www.leocook.org/ 另外,我建了个QQ群:3 ...
- Java中关于CountDownLatch的使用
CyclicBarrier工具类主要是控制多个线程的一起执行,演示程序: import java.util.Random; import java.util.concurrent.CountDownL ...
- 使用k8s cronjob ,清除应用生成的日志文件
目前应用日志,tomcat日志 统一输出到 /data/logs/pod名字/ 目录下,并且/data/logs 目录挂载到cephfs上, tomcat 日志使用 cronolog进行日志切割 使用 ...
- M600 (1)飞行注意事项
- 2017-2018-2 20155314《网络对抗技术》Exp2 后门原理与实践
2017-2018-2 20155314<网络对抗技术>Exp2 后门原理与实践 目录 实验要求 实验内容 实验环境 预备知识 1.后门概念 2.常用后门工具 实验步骤 1 用nc或net ...
- 转 jeecg3.5中多数据源的配置
官方微信有介绍通过web界面配置的方法:浅谈jeecg多数据源的使用,没试过不知道能不能用. 如果要手工配置也是可以的 在spring-mvc-hibernate.xml这个配置文件中增加一个数据源, ...