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之缓冲区的更多相关文章

  1. NIO 之 缓冲区(Buffer)

    缓存区是java nio的核心部分,所以必须熟悉它的一些操作. 实现类型: nio中实现了除布尔型(boolean)外的其他7种基本数据类型的buffer(ByteBuffer,CharBuffer, ...

  2. Java NIO 之缓冲区

    缓冲区基础 所有的缓冲区都具有四个属性来 供关于其所包含的数据元素的信息. capacity(容量):缓冲区能够容纳数据的最大值,创建缓冲区后不能改变. limit(上界):缓冲区的第一个不能被读或写 ...

  3. Java NIO之缓冲区Buffer

    Java NIO的核心部件: Buffer Channel Selector Buffer 是一个数组,但具有内部状态.如下4个索引: capacity:总容量 position:下一个要读取/写入的 ...

  4. Java NIO——2 缓冲区

    一.缓冲区基础 1.缓冲区并不是多线程安全的. 2.属性(容量.上界.位置.标记) capacity limit  第一个不能被读或写的元素 position  下一个要被读或写的元素索引 mark ...

  5. Java NIO Buffer缓冲区

    原文链接:http://tutorials.jenkov.com/java-nio/buffers.html Java NIO Buffers用于和NIO Channel交互.正如你已经知道的,我们从 ...

  6. NIO buffer 缓冲区 API

    package bhz.nio.test; import java.nio.IntBuffer; public class TestBuffer { public static void main(S ...

  7. NIO之缓冲区(Buffer)的数据存取

    缓冲区(Buffer) 一个用于特定基本数据类行的容器.有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类. Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道 ...

  8. NIO的缓冲区、通道、选择器关系理解

    Buffer的数据存取    一个用于特定基本数据类行的容器.有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类.   Java NIO中的Buffer主要用于与NIO通道进行交互,数 ...

  9. Java NIO ———— Buffer 缓冲区详解 入门

    引言缓冲区是一个用于特定基本类型的容器.由java.nio 包定义,所有缓冲区都是 Buffer 抽象类的子类. Java NIO 中的 Buffer ,主要用于与NIO 通道进行交互.数据从通道存入 ...

随机推荐

  1. Kotlin入门(9)函数的基本用法

    上一篇文章介绍了Kotlin新增的空安全机制,控制语句部分可算是讲完了,接下来将连续描述Kotlin如何定义和调用函数,本篇文章先介绍函数的基本用法. 前面几篇文章介绍控制语句之时,在setOnCli ...

  2. EF 更新部分字段写法

    EF 更新部分字段写法 1.EF默认是查询出来,修改后保存: 2.设置不修改字段的IsModified为false,此方法不需要先从数据库查询出实体来(最优方法): db.Set<T>() ...

  3. HashTree【转】

    http://blog.csdn.net/yang_yulei/article/details/46337405 在各种数据结构(线性表.树等)中,记录在结构中的相对位置是随机的.因此在机构中查找记录 ...

  4. nginx配置基于域名、端口、IP的虚拟主机

    1.基于域名的虚拟主机: 绝大多数企业对外提供服务的网站使用的都是基于域名的主机,通过不同的域名区分不同的虚拟主机. 首先我们进入安装nginxd的目录下:/application/nginx-1.6 ...

  5. 离群点检测与序列数据异常检测以及异常检测大杀器-iForest

    1. 异常检测简介 异常检测,它的任务是发现与大部分其他对象不同的对象,我们称为异常对象.异常检测算法已经广泛应用于电信.互联网和信用卡的诈骗检测.贷款审批.电子商务.网络入侵和天气预报等领域.这些异 ...

  6. Django电商项目---完成购物车页面day4

    创建购物车项目App python manage.py startapp df_cart 初始化项目: manas/urls.py manas/settings.py 创建新文件:df_cart/ur ...

  7. kafka 配置文件参数详解

    kafka的配置分为 broker.producter.consumer三个不同的配置 一 BROKER 的全局配置 最为核心的三个配置 broker.id.log.dir.zookeeper.con ...

  8. Visual Studio 2015的安装与测试单元的配置与使用

    首先感谢吴小勇同学的教程,及其详尽周全.详见http://www.cnblogs.com/xiaoyongwu/p/5289964.html     那么正式开始,事实上在撰写这篇博文的时候,这个图标 ...

  9. Lets encrypt安装及配置

    letsencrypt recommend that most people with shell access use the Certbot ACME client.It can automate ...

  10. 通过SQL直接插入、修改ArcGIS SDE空间表中的数据

    基于Arcgis Server 10.1 +Oracle 11g环境测试 ArcGIS SDE ? 1 2 INSERT INTO CAMERA_INFO(OBJECTID,ID,SHAPE)     ...