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. Source Insight里头文件注释和函数头的注释

    1.将下述代码拷贝入一个文件,扩展名为em 2.打开BASE工程,添加本文件,并重新同步 3.添加hh_InsertFuncHeader的快捷键,即为函数头注释,光标需要放在函数名那一行,否则无效 4 ...

  2. ORACLE11g下如何利用SQL DEVELOPER连接上数据库

    最近在学习数据库的相关内容,在sqlplus敲了几天命令行窗口后,想尝试一下用sql developer 连接上数据库但一直没有实现.在网上查询了相关资料后现在终于弄好了,就来写下此篇博文与大家分享! ...

  3. [20171107]dbms_shared_pool.pin.txt

    [20171107]dbms_shared_pool.pin.txt --//昨天与别人聊天提到,如果dbms_shared_pool.pin对象,可以改变对应的chunk的类型.我自己也不确定,做一 ...

  4. python第二十九天-----继续学习第三模块——前几天旅行去了

    subprocess模块 import subprocess subprocess.getstatusoutput('dir')#接收字符串格式命令,返回元组形式,第1个元素是执行状态,第2个是命令结 ...

  5. zTree 优秀的jquery树插件

    zTree 优秀的jquery树插件,文档详细,渲染快 使用方法: 1.引用zTree的js和css文件 <link href="~/Content/zTree_v3/css/zTre ...

  6. LVS (Linux Virtual Server) - 负载均衡集群 - keepalived

    今天稍微了解了LVS 的原理和使用,在网络上找到不少好文章,稍微加以处理并在这里备份: 原理介绍:Linux Virtual Server 关于:http://www.linuxvirtualserv ...

  7. DFS普及组常用模板简单整理

    一些普及组会用到的DFS模板,其他的DFS我感觉普及组不会用到所以暂且搁着,等之后有时间了再细写w (至于我为什么最近不写TG相关只写最基础的PJ的内容,请戳这里了解) dfs各种模板big集合 1. ...

  8. <20180923>中秋节日期间的维护日志

    (一) 陌生环境下断网是有风险的,提前做好准备. 1.1 某企业的机柜自2017年5月开始就没有作硬件更新和维护了: 趁着这次节日空挡可做一个机柜的重新整理维护: 首先看了下是老款某知名品牌的型号为6 ...

  9. luogu P2860 [USACO06JAN]冗余路径Redundant Paths

    题目描述 In order to get from one of the F (1 <= F <= 5,000) grazing fields (which are numbered 1- ...

  10. UVA804-Petri Net Simulation(模拟)

    Problem UVA804-Petri Net Simulation Accept:251  Submit:1975 Time Limit: 3000 mSec Problem Descriptio ...