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

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

非直接缓冲区

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

直接缓冲区

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

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

字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 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)数据写入物理内存缓冲区中,程序就失去了对这些数据的管理,即什么时候这些数据被最终写入从磁盘只能由操作系统来决定,应用程序无法再干涉。

选择方法

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

操作方法

1 @Test 2 public void test3(){ 3 ByteBuffer dirBuf = ByteBuffer.allocateDirect(1024); 4 if(dirBuf.isDirect()){ 5 System.out.println("dirBuf 是直接缓冲区"); 6 } 7 else{ 8 System.out.println("dirBuf 是非直接缓冲区"); 9 10 } 11 }

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

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

源码简析

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

1 /** 2 * Allocates a new direct byte buffer. 3 * 4 * <p> The new buffer's position will be zero, its limit will be its 5 * capacity, its mark will be undefined, and each of its elements will be 6 * initialized to zero. Whether or not it has a 7 * {@link #hasArray backing array} is unspecified. 8 * 9 * @param capacity 10 * The new buffer's capacity, in bytes 11 * 12 * @return The new byte buffer 13 * 14 * @throws IllegalArgumentException 15 * If the <tt>capacity</tt> is a negative integer 16 */ 17 public static ByteBuffer allocateDirect(int capacity) { 18 return new DirectByteBuffer(capacity); 19 }

我们再看DirectByteBuffer的构造实现:

1 // Primary constructor 2 // 3 DirectByteBuffer(int cap) { 4 5 super(-1, 0, cap, cap); 6 boolean pa = VM.isDirectMemoryPageAligned(); 7 int ps = Bits.pageSize(); 8 long size = Math.max(1L, (long)cap + (pa ? ps : 0)); 9 Bits.reserveMemory(size, cap); 10 11 long base = 0; 12 try { 13 base = unsafe.allocateMemory(size); 14 } catch (OutOfMemoryError x) { 15 Bits.unreserveMemory(size, cap); 16 throw x; 17 } 18 unsafe.setMemory(base, size, (byte) 0); 19 if (pa && (base % ps != 0)) { 20 // Round up to page boundary 21 address = base + ps - (base & (ps - 1)); 22 } else { 23 address = base; 24 } 25 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); 26 att = null; 27 28 }

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

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

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

1 /** 2 * Allocates a new byte buffer. 3 * 4 * <p> The new buffer's position will be zero, its limit will be its 5 * capacity, its mark will be undefined, and each of its elements will be 6 * initialized to zero. It will have a {@link #array backing array}, 7 * and its {@link #arrayOffset array offset} will be zero. 8 * 9 * @param capacity 10 * The new buffer's capacity, in bytes 11 * 12 * @return The new byte buffer 13 * 14 * @throws IllegalArgumentException 15 * If the <tt>capacity</tt> is a negative integer 16 */ 17 public static ByteBuffer allocate(int capacity) { 18 if (capacity < 0) 19 throw new IllegalArgumentException(); 20 return new HeapByteBuffer(capacity, capacity); 21 }

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

1 HeapByteBuffer(int cap, int lim) { 2 3 super(-1, 0, lim, cap, new byte[cap], 0); 4 /* 5 hb = new byte[cap]; 6 offset = 0; 7 */ 8 }
点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Netty(二)

一、先讲下NIO编程。NIO(NonblockI/O),亦叫做非阻塞I/O与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。1 缓冲区Buffer这里首先介绍缓冲区的概念,NIO和原I/O的一个重要区别就是NIO库中,所有数据都是用缓

JAVA NIO(一)

1NIO概述1.1NIO(newIO)是一个可以替代javaIOAPI的API,NIO提供了与标准IO不同的工作方式,标准IO是基于字节流和字符流进行操作的,而NIO是基于channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写到通道中,方式如例图示,JAVANIO的几个核心组成部分是channels,Buf

NIO通道(channel)原理与获取

一、通道(Channel):用于源节点与目标节点的连接。在javaNIO中负责缓冲区中数据的传输。Channel本身不存储数据,因此需要配合缓冲区进行传输。二、通道的主要实现类java.nio.channels.Channel接口:|–FileChannel|–SocketChannel|–ServerSocketCha