NIO学习笔记1

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中的说明不太容易理解,我们从源码层面来分析二者的区别。

 1 public abstract class Buffer {
 2     ............
 3     // Used only by direct buffers
 4     // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
 5     long address;
 6     ...........
 7 }

在Buffer类中定义了一个变量adress,注释为仅作为直接缓冲区使用,通过调用JNI的方法来获得一个内存地址。

也就是说,直接缓冲区说指向内存中的某个地址,而不是JVM中的某个区域。由于是内存中的某个区域,并且通过JNI去调用操作系统底层的指令,因此在某些情况下相对的高效。非直接缓冲区指向的是JVM内某个数组空间。

再来看创建直接缓冲区的一些有趣细节

 1 DirectByteBuffer(int cap) {
 2
 3     super(-1, 0, cap, cap, false);
 4     Bits.reserveMemory(cap);
 5     int ps = Bits.pageSize();//1、获取内存分页大小
 6     long base = 0;
 7     try {
 8         base = unsafe.allocateMemory(cap + ps);//2、分配内存空间,分配的空间比容量大,多出一个分页大小,为了后面调整起始位置也分页对齐
 9     } catch (OutOfMemoryError x) {
10         Bits.unreserveMemory(cap);
11         throw x;
12     }
13     unsafe.setMemory(base, cap + ps, (byte) 0);
14     if (base % ps != 0) {
15         // Round up to page boundary
16         address = base + ps - (base & (ps - 1));//3、缓冲区起始地址与分页对齐,方便寻址
17     } else {
18         address = base;
19     }
20     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

时间: 2024-10-09 05:31:49

NIO学习笔记1的相关文章

java nio学习笔记(一)

位置保留,待用 java nio学习笔记(一),布布扣,bubuko.com

我的NIO学习笔记

一.文章来由 研究Nio也有几天了,在网上看了很多文章,给人整体的感觉就是,一个原本简简单单的东西,被说的好复杂.或者是类似 http://ifeve.com/selectors/ 这种百科全书式的教你如何用接口,这种文章看似介绍了每个函数,面面俱到,却很难串起来. 但是人学东西本来就是一个感性认识到理性认识的过程,如果看到的都是单点,怎么在脑海中形成一张图? (1)是什么(功能) (2)为什么这么设计(应用场景) (3)怎么实现的(具体代码) 这三个问号搞完了以后,才是如何去使用,不能本末倒置

java NIO 学习笔记(一)

相关概念:缓冲区和通道 解释:标准的IO流是基于字节流和字符流的而NIO是基于通道和缓冲区的,数据总是从通道读取到缓冲区或者从缓冲区读取到通道的. 相关概念:非阻塞IO 解释:当线程从通道读取数据到缓冲区时线程同事还可以进行其他的事情. 相关概念:selectors(选择器) 解释:单个线程可以监听多个通信通道.

Java NIO学习笔记(一)

文章目录: 1.什么是IO 2.什么是Java NIO 3.I/O常见概念 4.为什么使用NIO 5.IO VS NIO 一.什么是IO I/O 或者输入/输出 , 指的是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口.它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的.单独的程序一般是让系统为它们完成大部分的工作.在 Java 编程中,直到最近一直使用 流 的方式完成 I/O.所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象

java NIO 学习笔记

项目组是做IM产品的,服务端当然用的是NIO技术做通信底层.但是一直都是对NIO有些理论的了解,没有实践,最近有空了,就实践了下NIO. NIO,新IO,也称之为非阻塞IO.非阻塞是它跟传统IO的最重要的区别之一.传统IO用Socket进行通信,NIO则用channel进行消息交互.channel必须注册到selector上,把它感兴趣的事件告诉selector.这是个观察者模式的实现.可以这样描述channel和selector的关系,channel是火车轨道,selector是火车调度室.多

Java NIO学习笔记

NIO非堵塞应用通常适用用在I/O读写等方面,我们知道,系统运行的性能瓶颈通常在I/O读写,包括对端口和文件的操作上,过去,在打开一个I/O通道后,read()将一直等待在端口一边读取字节内容,如果没有内容进来,read()也是傻傻的等,这会影响我们程序继续做其他事情,那么改进做法就是开设线程,让线程去等待,但是这样做也是相当耗费资源的. Java NIO非堵塞技术实际是采取Reactor模式,或者说是Observer模式为我们监察I/O端口,如果有内容进来,会自动通知我们,这样,我们就不必开启

Java NIO学习笔记八 DatagramChannel

Java NIO DatagramChannel Java NIO DatagramChannel是可以发送和接收UDP数据包的通道.由于UDP是一种无连接网络协议,因此您不能默认读取和写入DatagramChannel其他通道.而是发送和接收数据包. 打开DatagramChannel 打开一个DatagramChannel代码: DatagramChannel channel = DatagramChannel.open(); channel.socket().bind(new InetSo

Java NIO学习笔记七 Non-blocking Server

Java NIO:Non-blocking Server 即使你了解了Java NIO非阻塞功能的工作(怎么样Selector,Channel, Buffer等等),设计一个无阻塞服务器仍然很难.非阻塞IO包含了相比阻塞IO的要有难度.本章非阻塞服务器教程将讨论非阻塞服务器的主要挑战,并为他们描述一些潜在的解决方案. 本教程中描述的想法是围绕Java NIO设计的.但是,我认为,只要有这样的构造,这些想法可以用其他语言重复使用Selector.据我所知,这样的结构是由底层操作系统提供的,所以很有

nio学习笔记

Java NIO(New IO或 Non Blocking IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.NIO支持面向缓冲区的.基于通道的IO操作.NIO将以更加高效的方式进行文件的读写操作. java IO 与 java NIO 的区别  一.通道(Channel)与缓冲区(Buffer) 若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区.然后操作缓冲区,对数据进行处理.简而言之,Channel 负责传