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 通道进行交互.数据从通道存入 ...
 
随机推荐
- [Android GMS 认证] keystore/keymaster/Attestation的问题
			
首先确定写入key,操作如下: 检查 /persist/data/sfs 目录下是否有key文件存在 adb shell ls -la /persist/data/sfs 做过key prov ...
 - 使用缓存方式优化递归函数与lru_cache
			
一.递归函数的弊端 递归函数虽然编写时用很少的代码完成了庞大的功能,但是它的弊端确实非常明显的,那就是时间与空间的消耗. 用一个斐波那契数列来举例 import time #@lru_cache(20 ...
 - Django电商项目---完成订单页面day5
			
完成订单页面 创建订单项目 python manage.py startapp df_order manas/settings.py manas/urls.py 创建静态文件: templates/d ...
 - Matplotlib:可视化颜色命名分类和映射颜色分类
			
Matplotlib中支持的所有颜色分类 映射颜色分类
 - 八皇后问题的Python实现和C#实现
			
看到八皇后问题的解决思路, 感觉很喜欢. 我用C#实现的版本之前贴在了百度百科上(https://baike.baidu.com/item/%E5%85%AB%E7%9A%87%E5%90%8E%E9 ...
 - 阿里八八β阶段Scrum(1/5)
			
今日进度 叶文滔: 修改了α阶段遗留的部分界面BUG,比如状态栏白底等 张岳: 修复用户模块信息修改返回失败的BUG 林炜鸿: 重构了添加事件的代码,增加可修改的特性 黄梅玲: 绘制了新的日程显示格界 ...
 - 建立标准编码规则(一)-自定义C#代码分析器
			
1.下载Roslyn的Visual Studio分析器模板插件(VS2015 或VS2017) https://marketplace.visualstudio.com/items?itemName= ...
 - DataUtils对Connection的获取、释放和关闭的操作学习
			
DataSourceUitls介绍 DataSourceUitls类位于org.springframework.jdbc.datasource包下,提供了很多的静态方法去从一个javax.sql.Da ...
 - (转)Spring boot(一):入门篇
			
https://www.cnblogs.com/ityouknow/p/5662753.html#!comments 构建微服务:Spring boot 入门篇 什么是Spring Boot Spri ...
 - CentOS 7下安装Python3.6.4
			
CentOS 7下安装Python3.5 •安装python3.6可能使用的依赖 yum install openssl-devel bzip2-devel expat-devel gdbm-deve ...