JAVA NIO 直接缓冲区和非直接缓冲区

Wesley13
• 阅读 663

前面我们一直说NIO能够提高性能,那么到底如何提高效率。本篇就接着上一篇文章的缓冲区,来看看直接缓冲区和非直接缓冲区。

非直接缓冲区

首先看看非直接缓冲区。我们之前说过NIO通过通道连接磁盘文件与应用程序,通过缓冲区存取数据进行双向的数据传输。物理磁盘的存取是操作系统进行管理的,与物理磁盘的数据操作需要经过内核地址空间;而我们的Java应用程序是通过JVM分配的缓冲空间。有点雷同于一个属于核心态,一个属于应用态的意思,而数据需要在内核地址空间和用户地址空间,在操作系统和JVM之间进行数据的来回拷贝,无形中增加的中间环节使得效率与后面要提的之间缓冲区相比偏低。

JAVA NIO 直接缓冲区和非直接缓冲区

直接缓冲区

直接缓冲区则不再通过内核地址空间和用户地址空间的缓存数据的复制传递,而是在物理内存中申请了一块空间,这块空间映射到内核地址空间和用户地址空间,应用程序与磁盘之间的数据存取之间通过这块直接申请的物理内存进行。

JAVA NIO 直接缓冲区和非直接缓冲区

直接与非直接缓冲区的要点

字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用操作系统基础的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。

直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在 直接缓冲区能在程序性能方面带来明显好处时 分配它们。

直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer 。 Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。

字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理

(本文出自oschina博主happyBKs的博文:https://my.oschina.net/happyBKs/blog/1592329)

那么既然直接缓冲区的性能更高、效率更快,为什么还要存在两种缓冲区呢?因为直接缓冲区也存在着一些缺点:

(1)不安全;

(2)消耗更多,因为它不是在JVM中直接开辟空间。这部分内存的回收只能依赖于垃圾回收机制,垃圾什么时候回收不受我们控制;

(3)数据写入物理内存缓冲区中,程序就失去了对这些数据的管理,即什么时候这些数据被最终写入从磁盘只能由操作系统来决定,应用程序无法再干涉。

选择方法

直接缓冲区适合与数据长时间存在于内存,或者大数据量的操作时更加适合。

操作方法

    @Test
    public void test3(){
        ByteBuffer dirBuf = ByteBuffer.allocateDirect(1024);
        if(dirBuf.isDirect()){
            System.out.println("dirBuf 是直接缓冲区");
        }
        else{
            System.out.println("dirBuf 是非直接缓冲区");

        }
    }

这是第一种建立直接缓冲区的方法。

第二种建立直接缓冲区的方式:建立内存映射文件,来建立直接缓冲区。

源码简析

我们可以看看直接缓冲区申请的源码:

    /**
     * Allocates a new direct byte buffer.
     *
     * <p> The new buffer's position will be zero, its limit will be its
     * capacity, its mark will be undefined, and each of its elements will be
     * initialized to zero.  Whether or not it has a
     * {@link #hasArray backing array} is unspecified.
     *
     * @param  capacity
     *         The new buffer's capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

我们再看DirectByteBuffer的构造实现:

   // Primary constructor
    //
    DirectByteBuffer(int cap) {                   

        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;

    }

可以看到unsafe.allocateMemory(size),已经不能进入源码了,它已经是操作系统层面的jni调用了。

我们再看看非直接缓冲区的申请源码:

它申请的是堆空间,即在JVM上的操作。

    /**
     * Allocates a new byte buffer.
     *
     * <p> The new buffer's position will be zero, its limit will be its
     * capacity, its mark will be undefined, and each of its elements will be
     * initialized to zero.  It will have a {@link #array backing array},
     * and its {@link #arrayOffset array offset} will be zero.
     *
     * @param  capacity
     *         The new buffer's capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }

再进一层,我们已经可以看到byte数组了。

    HeapByteBuffer(int cap, int lim) {            

        super(-1, 0, lim, cap, new byte[cap], 0);
        /*
        hb = new byte[cap];
        offset = 0;
        */
    }
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Netty(二)
一、先讲下NIO编程。NIO(NonblockI/O),亦叫做非阻塞I/O与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。1 缓冲区Buffer这里首先介绍缓冲区的概念,NIO和原I/O的一个重要区别就是NIO库中,所有数据都是用缓
Wesley13 Wesley13
3年前
JAVA NIO(一)
1NIO概述1.1NIO(newIO)是一个可以替代javaIOAPI的API,NIO提供了与标准IO不同的工作方式,标准IO是基于字节流和字符流进行操作的,而NIO是基于channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写到通道中,方式如例图示,JAVANIO的几个核心组成部分是channels,Buf
Wesley13 Wesley13
3年前
Java NIO学习笔记
JavaNIO是什么JavaNIO(NewIO)是从Java1.4版本开始引入的一个新的IOAPI,可以替代标准的JavaIOAPI。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。JavaNIO与IO
Wesley13 Wesley13
3年前
NIO通道(channel)原理与获取
一、通道(Channel):用于源节点与目标节点的连接。在javaNIO中负责缓冲区中数据的传输。Channel本身不存储数据,因此需要配合缓冲区进行传输。二、通道的主要实现类java.nio.channels.Channel接口:|–FileChannel|–SocketChannel|–ServerSocketCha
Stella981 Stella981
3年前
Netty之缓冲区ByteBuf解读(一)
!(https://oscimg.oschina.net/oscnet/up6de4d71f462d9846befe00ec6505125a928.JPEG)\Netty在数据传输过程中,会使用缓冲区设计来提高传输效率。虽然,Java在NIO编程中已提供ByteBuffer类进行使用,但是在使用过程中,其编码方式相对来说不太友好,也
Wesley13 Wesley13
3年前
Java NIO编程学习总结
目录1、同步,异步,阻塞,非阻塞的理解2、什么是BIO、NIO、AIO3、javaNIO常用类和方法介绍3.1.缓冲区Buffer3.2.通道Channel3.2.1FileChannel3.2.2SocketChan
Wesley13 Wesley13
3年前
NIO 非阻塞IO
NIO与IO的区别NIO特点:非阻塞,面向缓冲区IO特点:阻塞式,面向流阻塞与非阻塞javaio是阻塞式的,当一个线程调用read或者write方法后开始阻塞,直到读取到数据或者写入数据完成,该线程一直处于阻塞状态不能做其他事情。javanio通过选择器实现非阻塞式IO,通过一个专门的选
Wesley13 Wesley13
3年前
NIO直接缓冲区与非直接缓冲区
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm内存中。直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率。!这里写图片描述(https://static.oschina.net/uploads/img/201709/06150240_bPZ3.jpg)!