`
春花秋月何时了
  • 浏览: 40011 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Java NIO MappedByteBuffer

阅读更多

前言

java通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理文件读写,不过java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高。MappedByteBuffer引入了内存映射文件的方法,该方案是建立的操作系统的内存管理机制上的。

 

操作系统的内存管理机制

操作系统的内存分为:物理内存进程虚拟地址空间(即逻辑地址空间),物理地址大家都知道,就是真实的物理,那什么是进程虚拟地址空间?

原来当每次创建一个进程的时候,操作系统都会为该进程分配一块虚拟地址空间,如果是32位的操作系统就是4GB大小,之所以是4GB,是因为在32位的操作系统中,一个指针长度是4字节,而4字节指针的寻址能力是从0x00000000~0xFFFFFFFF,最大值0xFFFFFFFF表示的即为4GB大小的容量。这4GB的虚拟地址空间分为2GB用户空间,2GB内核空间,进程应用程序只能访问自己对应的那2GB用户空间,而2GB的内核空间数据被所有应用程序共享,但是应用程序是不能直接访问。这两种地址空间的产生是为了隔离应用程序的数据,防止被除自己以为的其他应用程序恶意篡改。

此外,为了提高内存使用效率产生了分页机制,将物理内存和进程虚拟地址空间进行分页,页的大小由CPU决定,并且对与这两种地址空间产生的页的大小是相同的,例如,如果按照每页4KB的大小,4GB虚拟地址空间共可以分成1048576个页,512M的物理内存可以分为131072个页。显然虚拟空间的页数要比物理空间的页数多得多,在程序运行时,用到哪些页的数据就加载哪些页的数据到内存进行分配内存,并建立虚拟地址空间中的页和刚分配的物理内存页间的映射,没用到的页暂时保留在硬盘上。

一个完整的可执行应用程序的装载过程如下:一个可执行文件其实就是一些编译好的数据和指令的集合,它也会被分成很多页,为其分配虚拟地址空间的过程中会创建将来要进行内存映射的数据结构,这种数据结构就是页目和页表,当创建完这种数据结构之后,将把应用程序的数据一一映射到虚拟地址空间相应的页中,这时并没有真正将数据加载到内存,当CPU访问程序中用到的某一个虚拟地址,发现该地址并没有相关联的物理地址时,CPU会认为这是个页错误(Page Fault),从而知道操作系统还未给该虚拟页分配内存,CPU会将控制权交还给操作系统,操作系统在物理内存中为其分配页面,然后再将这个物理页面与虚拟空间中的虚拟页面映射起来,从而程序得以继续执行,这种页的加载有时候也被叫做缺页中断。值得注意的,当物理内存不够使用时,操作系统可以找到最少使用的页,将其失效,并将其回写到硬盘,修改映射关系,留出空余空间。

 

MappedByteBuffer原理

从继承结构上看,MappedByteBuffer继承自ByteBuffer,FileChannel提供了map方法把文件映射到进程虚拟地址空间,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。

FileChannel的Map方法的MapMode参数指定了内存映像文件访问的方式,共三种:

  1. MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
  2. MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
  3. MapMode.PRIVATE:私用,可读可写,但是修改的内容不会写入文件,只是buffer自身的改变,这种能力称之为”copy on write”。

 

public MappedByteBuffer map(MapMode mode, long position, long size)  throws IOException {
     int pagePosition = (int)(position % allocationGranularity);
     long mapPosition = position - pagePosition;
     long mapSize = size + pagePosition;
     try {
         addr = map0(imode, mapPosition, mapSize);
     } catch (OutOfMemoryError x) {
         System.gc();
         try {
             Thread.sleep(100);
         } catch (InterruptedException y) {
             Thread.currentThread().interrupt();
         }
         try {
             addr = map0(imode, mapPosition, mapSize);
         } catch (OutOfMemoryError y) {
             // After a second OOME, fail
             throw new IOException("Map failed", y);
         }
     }
     int isize = (int)size;
     Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
     if ((!writable) || (imode == MAP_RO)) {
         return Util.newMappedByteBufferR(isize,
                                          addr + pagePosition,
                                          mfd,
                                          um);
     } else {
         return Util.newMappedByteBuffer(isize,
                                         addr + pagePosition,
                                         mfd,
                                         um);
     }
}

 从源代码可以看出,最终map通过native函数map0完成文件的映射工作,并返回一个进程虚拟地址addr,第一次文件映射导致OOM,则手动触发垃圾回收,休眠100ms后再次尝试映射,如果失败,则抛出异常。

 

static MappedByteBuffer newMappedByteBuffer(int size, long addr, FileDescriptor fd, Runnable unmapper) {
MappedByteBuffer dbb;
if (directByteBufferConstructor == null)
 initDBBConstructor();
dbb = (MappedByteBuffer)directByteBufferConstructor.newInstance(
   new Object[] { new Integer(size),
                  new Long(addr),
                  fd,
                  unmapper }
return dbb;
}
// 访问权限
private static void initDBBConstructor() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
 public Void run() {
     Class<?> cl = Class.forName("java.nio.DirectByteBuffer");
         Constructor<?> ctor = cl.getDeclaredConstructor(
             new Class<?>[] { int.class,
                              long.class,
                              FileDescriptor.class,
                              Runnable.class });
         ctor.setAccessible(true);
         directByteBufferConstructor = ctor;
 }});
}

 最后返回的MappedByteBuffer实例是DirectByteBuffer类型,其实现了对内存的直接操作。

 

MappedByteBuffer的get方法其实是调用了DirectByteBuffer的get放大:

 

public byte get() {
    return ((unsafe.getByte(ix(nextGetIndex()))));
}
public byte get(int i) {
    return ((unsafe.getByte(ix(checkIndex(i)))));
}
private long ix(int i) {
    return address + (i << 0);
}

 可以看出,MappedByteBuffer是通过map0方法返回的进程虚拟地址和偏移量进行操作文件,因为map0方法对数据和进程虚拟地址空间进行了映射,通过缺页中断机制可以进行文件的分段加载,代码中使用了unsafe.getByte方法,可见数据是直接分配的物理内存,而不是JVM的内存空间。

 


性能浅析:

  • read()是系统调用,首先将文件从硬盘拷贝到内核空间的一个缓冲区,再将这些数据拷贝到用户空间,实际上进行了两次数据拷贝;
  • map()也是系统调用,但没有进行数据拷贝,当缺页中断发生时,直接将文件从硬盘拷贝到用户空间,只进行了一次数据拷贝。

MappedByteBuffer的缺陷

使用MappedByteBuffer内存占用、文件关闭不确定,被其打开的文件只有在垃圾回收的才会被关闭,而且这个时间点是不确定的。

网络流传的解决方案如下:

AccessController.doPrivileged(new PrivilegedAction() {  
  public Object run() {  
    try {  
      Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);  
      getCleanerMethod.setAccessible(true);  
      sun.misc.Cleaner cleaner = (sun.misc.Cleaner)   
      getCleanerMethod.invoke(byteBuffer, new Object[0]);  
      cleaner.clean();  
    } catch (Exception e) {  
      e.printStackTrace();  
    }  
    return null;  
  }  
});  

 

话外语:

在利用FileChannel进行map映射内存文件的时候,一个文件可以被多个应用程序进行映射,事实上,这也是一种对于超大型文件在不同进程间数据共享的一种方式。

 

 

MappedByteBuffer的实例

 

public void testMappedByte() throws FileNotFoundException, IOException {
		long start = System.currentTimeMillis();
		File file = new File("E:\\工作目录\\新项目\\凤舞一期\\backup\\new_show_style_json.txt");
		long fileLength = file.length();
		final int BUFFER_SIZE = 0x500000;// 5M
		MappedByteBuffer inputBuffer = new RandomAccessFile(file, "rw").getChannel().map(FileChannel.MapMode.READ_WRITE,
				0, fileLength);
		byte[] dst = new byte[BUFFER_SIZE];
		int count = 0;
		for (int offset = 0; offset < fileLength; offset += BUFFER_SIZE) {
			if (fileLength - offset >= BUFFER_SIZE) {
				for (int i = 0; i < BUFFER_SIZE; i++)
					dst[i] = inputBuffer.get(offset + i);
			} else {
				for (int i = 0; i < fileLength - offset; i++)
					dst[i] = inputBuffer.get(offset + i);
			}
			String bs = new String(dst, "UTF-8");// 将buffer中的字节转成字符串
			String[] ns = bs.split("\n");
			for (String s : ns) {
				if (s.contains(" -1- ")) {
					count++;
					System.out.println(s.split("- 1 -")[0]);
				}
			}

			System.out.println();
			// String s = IOUtils.toString(new ByteArrayInputStream(dst));
			// System.out.println(s);
		}
		System.out.println("总处理条数:" + count);
		long end = System.currentTimeMillis();

		System.out.println((end - start) / 1000);// 处理809M的文件,90000条数据,只用了6秒
	}

 

 

分享到:
评论

相关推荐

    java NIO和java并发编程的书籍

    java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java...

    JavaNIO chm帮助文档

    Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel Java NIO系列教程(三) Buffer Java NIO系列教程(四) Scatter/Gather Java NIO系列教程(五) 通道之间的数据传输 Java NIO系列教程(六)...

    java nio 包读取超大数据文件

    Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据...

    Java NIO英文高清原版

    Java NIO英文高清原版

    java NIO 中文版

    讲解了 JavaIO 与 JAVA NIO区别,JAVA NIO设计理念,以及JDK中java NIO中语法的使用

    java NIO.zip

    java NIO.zip

    Java NIO 中文 Java NIO 中文 Java NIO 中文文档

    Java NIO 深入探讨了 1.4 版的 I/O 新特性,并告诉您如何使用这些特性来极大地提升您所写的 Java 代码的执行效率。这本小册子就程序员所面临的有代表性的 I/O 问题作了详尽阐述,并讲解了 如何才能充分利用新的 I/O ...

    java NIO 视频教程

    Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,...

    java nio 实现socket

    java nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socket

    java nio中文版

    java NIO是 java New IO 的简称,在 jdk1.4 里提供的新 api 。 Sun 官方标榜的特性如下: – 为所有的原始类型提供 (Buffer) 缓存支持。 – 字符集编码解码解决方案。 – Channel :一个新的原始 I/O 抽象。 – 支持...

    基于Java NIO实现五子棋游戏.zip

    基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现...

    Java Nio selector例程

    java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server发数据,server收到后分别打印收到的消息...

    java NIO技巧及原理

    java NIO技巧及原理解析,java IO原理,NIO框架分析,性能比较

    java基于NIO实现Reactor模型源码.zip

    java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现...

    java nio 读文件

    java nio 读文件,java nio 读文件

    Java NIO实战开发多人聊天室

    01-Java NIO-课程简介.mp4 05-Java NIO-Channel-FileChannel详解(一).mp4 06-Java NIO-Channel-FileChannel详解(二).mp4 08-Java NIO-Channel-ServerSocketChannel.mp4 09-Java NIO-Channel-SocketChannel.mp4 ...

    JAVA NIO 学习资料

    JAVA NIO学习资料JAVA NIO学习资料

    java nio入门学习,两个pdf

    java nio入门学习,两个pdfjava nio入门学习,两个pdf

    Java NIO (中文版)

    Java NIO (中文版) PDF文档,带目录,非图片扫描 !!!

    Java NIO.pdf

    java nio编程 非阻塞模式的通信 电子书 带目录标签

Global site tag (gtag.js) - Google Analytics