计算机

Java IO与NIO浅谈

2018-08-29  本文已影响2人  春光明媚的羊

一、传统IO模式下的文件读取
传统的文件IO操作都是调用OS提供的底层标准IO操作读取函数read()、write();然后调用此函数的进程(即java进程)由java用户态切换至内核态。然后OS内核代码负责将相应的文件读取到内核IO缓存之中,然后再把数据从内核IO缓存之中拷贝到进程相应的私有地址空间中。则完成一次IO操作;

Q&A:

Q:为什么要搞一个内核IO缓存,将原本拷贝一次数据的操作整两次?

A:因为为了减少磁盘的IO操作,提升性能;因为我们的程序具有局部性,即所谓的局部性原理,在这里是空间局部性;即我们访问文件中的一段数据,接下来可能还会访问接下去的一段数据,而磁盘的IO操作相对于内存慢了好几个数量级,所以OS根据局部性原理会在read()时,会预读更多的文件数据存放在内核IO缓存中,当访问的文件数据在内核IO缓冲区之中时直接将数据拷贝到进程私有内存地址之中(也有可能经过了native堆中转,因为这些函数都是声明为native本地平台相关)。避免低效率的磁盘IO读取。

Q:既然有内核IO缓存,那java为什么还提供BufferedInputStream对象?

A:因为从内核IO缓存中拷贝到进程私有空间数据系统调用,而系统调用相对来数代价是比较高的,需要从java用户态和内核态的上下文切换。

二,java NIO读取模式

  1. java内存映射文件读取模式

简介:将进程的用户私有空间的一部分区域与文件对象建立映射关系。并不需要将文件拷贝到内核IO缓存之中,类似于直接从内存中读取数据,这样速度当然快。

java之中针对于内存映射提供了三种模式:只读(readonly)、读写(read_write)、专有(private);

备注:

package com.seriousty.practice.memory;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.nio.ByteBuffer;

import java.nio.MappedByteBuffer;

import java.nio.channels.FileChannel;

/**

* Created with IntelliJ IDEA

* Created By seriousty

* Date: 2018/8/24

* Time: 15:05

* BOLG: [https://github.com/seriousty](https://github.com/seriousty)

* Description:

*/

public class IOTest {

private static int MAX_SIZE = 1024;

/**

*NIO操作:

*/

public static void main(String[] args) {

File file=new File("[h://iofile.pdf](file:///h://iofile.pdf)");

FileInputStream fis = null;

try {

fis = new FileInputStream(file);

FileChannel channel = fis.getChannel();

MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());//time consuming:39

byte[] bytes=new byte[1024];

int length = (int) file.length();

long start = System.currentTimeMillis();

for (int i = 0; i <length ; i+=1024) {

if(length-i>MAX_SIZE){

map.get(bytes);

}else{

map.get(new byte[length-i]);

}

}

long end = System.currentTimeMillis();

System.out.println("time consuming:"+(end-start));//time consuming:41

} catch (Exception e) {

e.printStackTrace();

}

}

}
普通IO模式:

public static void main(String[] args) {

File file = new File("[h://iofile.pdf](file:///h://iofile.pdf)");

try {

FileInputStream fis = new FileInputStream(file);

FileChannel channel = fis.getChannel();

 ByteBuffer allocate = ByteBuffer.allocate(1024);

//ByteBuffer allocate = ByteBuffer.allocateDirect((int) channel.size());

long start = System.currentTimeMillis();

while (channel.read(allocate) != -1) {

allocate.flip();

allocate.clear();

}

long end = System.currentTimeMillis();

System.out.println("time consuming:"+(end-start));//time consuming:1014

} catch (Exception e) {

e.printStackTrace();

}

}

2,直接内存(Directed Memory)

给直接内存设置最大内存大小

image.png

给直接内存分配内存


image.png

结论:

执行了多次Full GC,没有执行GC(不受新生代的影响),只有当回收老年代的时候再回顺便回收直接内存?why,因为直接内存是通过DirectByteBuffer对象来引用的,所以当DirectByteBuffer对象由新生代进入老年代后,在老年代触发了Full GC.

总结:

NIO中的DirectMemory和内存映射文件都是直接内存缓冲,但是DirectMemory能通过JVM -XX:+Xmx和-XX:MaxDirectMemorySize参数来控制,内存映射文件没有JVM参数可以控制;

两者内存分配位置:

DirectMemory:在java进程中的native堆中分配,不受young gc控制,避免了在java堆和native堆中copy,提高文件传输的性能

放java向外进行数据传输时,需要先将数据从java堆拷贝到native堆之中。

内存映射文件:没有经过native堆,由java进程私有内存空间一部分于文件对象建立关联的映射关系。所以也不会受young gc影响。

上一篇 下一篇

猜你喜欢

热点阅读