磁盘的IO因为速度较慢,可能成为系统运行的瓶颈。所以磁盘的IO在操作系统级实现了提前读,延迟写的机制来提升IO的性能。
提前读就是一次读取需求的数据的同时多读接下来的一段数据至OS缓冲区中,延迟写就是待OS缓冲区中的数据到了一定量时一次写入。
Java中的Bufferxxx类也提供了缓冲区来完成提前读的功能,如果下一次需要读的数据已经在缓冲区了,就直接从缓冲区中取数据。
NIO的内存映射不在直接读文件到OS的缓冲区中,而是直接在JVM中为文件分配了一段内存区域,就像直接在内存中读取文件一样。速度较传统的IO要快很多,文件体积较小时速度差异不是很明显,但文件体积较大时速度的差异就会很明显。
package nio; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; public class MappedByteBufferTest { private static String sourceFileName = "UCS_PM_1.1.5_EN.sql"; private static String dstFileNameByMappedBuffer = "new.sql"; private static String dstFileNameByFileCopy = "new1.sql"; private static String dstFileNameByAllocateDirect = "new2.sql"; private static FileChannel readChannel, writeChannel; private static long start; private static long length; public static void main(String[] args) throws Exception { mapBufferWriteFile(); //使用内存映射流来完成文件复制 testFileCopy(); //使用普通的IO流来完成文件复制 allocateDirectWriteFile(); //使用DirectMemory来完成文件复制 } public static void mapBufferWriteFile() { try { start = System.currentTimeMillis(); RandomAccessFile fos = new RandomAccessFile(dstFileNameByMappedBuffer, "rw"); writeChannel = fos.getChannel(); //writeChannel.position(writeChannel.size()); append to file end position RandomAccessFile fis = new RandomAccessFile(sourceFileName, "r"); readChannel = fis.getChannel(); length = readChannel.size(); MappedByteBuffer mbb = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, length); writeChannel.write(mbb); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { readChannel.close(); writeChannel.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println("mapBufferWriteFile:" + (System.currentTimeMillis() - start)); } } private static void testFileCopy() { start = System.currentTimeMillis(); BufferedInputStream bis =null; BufferedOutputStream bos = null; try { bis = new BufferedInputStream(new FileInputStream(sourceFileName)); bos = new BufferedOutputStream(new FileOutputStream(dstFileNameByFileCopy)); byte[] bytes = new byte[1024]; int index = 0; int b = bis.read(); while(b != -1){ bytes[index] = (byte) b; index ++; if(index == 1023){ bos.write(bytes, 0, index); index = 0; b = bis.read(); }else{ b = bis.read(); } } if(index != 1023){ bos.write(bytes, 0, index); } } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); }finally { try { bis.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("testFileCopy:" + (System.currentTimeMillis() - start)); } private static void allocateDirectWriteFile() { try { start = System.currentTimeMillis(); RandomAccessFile fos = new RandomAccessFile(dstFileNameByAllocateDirect, "rw"); writeChannel = fos.getChannel(); RandomAccessFile fis = new RandomAccessFile(sourceFileName, "r"); readChannel = fis.getChannel(); length = readChannel.size(); ByteBuffer mbb = ByteBuffer.allocate((int)readChannel.size()); readChannel.read(mbb); mbb.flip(); writeChannel.write(mbb); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { readChannel.close(); writeChannel.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println("allocateDirectWriteFile:" + (System.currentTimeMillis() - start)); } } }
NIO中提供了3种内存映射模式,即:只读(readonly)、读写(read_write)、专用(private) ,对于 只读模式来说,如果程序试图进行写操作,则会抛出ReadOnlyBufferException异常;第二种的读写模式表明了通过内存映射文件的方式写或修改文件内容的话是会立刻反映到磁盘文件中去的,别的进程如果共享了同一个映射文件,那么也会立即看到变化。
除了内存映射之外,还有一种快速读取文件的方式,叫做DirectMemory。DirectMemory也是将文件映射到内存中,只不过占用的不再是JVM的内存,而是非JVM的内存。
直接内存DirectMemory的大小默认为 -Xmx 的JVM堆的最大值,但是并不受其限制,而是由JVM参数 MaxDirectMemorySize单独控制。
DirectMemory所占用的内存无法手动的去释放,只能在 JVM执行 full gc 的时候才会被回收,那么如果在其上分配过大的内存空间,那么也将出现 OutofMemoryError。
如果系统中用到了DirectMemory,则一定要监控其使用的情况,否则很容易导致系统运行缓慢。
-Xms -Xmx不只局限于物理内存的大小,而是也综合虚拟内存的大小,JVM会根据电脑虚拟内存的设置来调节。