简单总结:
1、内存映射文件
读文件时候一般要两次复制:从磁盘复制到内核空间再复制到用户空间,内存映射文件避免了第二次复制,且内存分配在内核空间,应用程序访问的就是操作系统的内核内存空间,因此极大提高了读取效率。写文件同理。
2、堆内存分配与直接内存分配:
Java申请空间时通常是从JVM堆内存分配的,即 ByteBuffer.allocate(int capacity) ,但其实还可以直接从物理内存(用户空间内存?)分配,即 ByteBuffer.allocateDirect(int capacity) ,后者其实调用了Unsafe类进行分配(见下节)。后者的分配原理是这样的:使用Native函数库直接分配堆外内存,通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,从而避免了在java堆和Native堆之间复制数据的开销。
通常来说,由于后者避免了数据在堆外内存和JVM堆内存间的复制,所以读写性能比前者的好,但是后者的分配比前者慢,特别是在数据量大的情况下差别更明显。此外,直接内存常被用来扩展可用的内存区域。
比较:
1 class DirectMemory { 2 3 // 分配堆内存 4 public static void bufferAccess() { 5 long startTime = System.currentTimeMillis(); 6 ByteBuffer b = ByteBuffer.allocate(500); 7 for (int i = 0; i < 1000000; i++) { 8 for (int j = 0; j < 99; j++) 9 b.putInt(j); 10 b.flip(); 11 for (int j = 0; j < 99; j++) 12 b.getInt(); 13 b.clear(); 14 } 15 long endTime = System.currentTimeMillis(); 16 System.out.println("access_nondirect:" + (endTime - startTime)); 17 } 18 19 // 直接分配内存 20 public static void directAccess() { 21 long startTime = System.currentTimeMillis(); 22 ByteBuffer b = ByteBuffer.allocateDirect(500); 23 for (int i = 0; i < 1000000; i++) { 24 for (int j = 0; j < 99; j++) 25 b.putInt(j); 26 b.flip(); 27 for (int j = 0; j < 99; j++) 28 b.getInt(); 29 b.clear(); 30 } 31 long endTime = System.currentTimeMillis(); 32 System.out.println("access_direct:" + (endTime - startTime)); 33 } 34 35 public static void bufferAllocate() { 36 long startTime = System.currentTimeMillis(); 37 for (int i = 0; i < 1000000; i++) { 38 ByteBuffer.allocate(1000); 39 } 40 long endTime = System.currentTimeMillis(); 41 System.out.println("allocate_nondirect:" + (endTime - startTime)); 42 } 43 44 public static void directAllocate() { 45 long startTime = System.currentTimeMillis(); 46 for (int i = 0; i < 1000000; i++) { 47 ByteBuffer.allocateDirect(1000); 48 } 49 long endTime = System.currentTimeMillis(); 50 System.out.println("allocate_direct:" + (endTime - startTime)); 51 } 52 53 public static void main(String args[]) { 54 System.out.println("访问性能测试:"); 55 bufferAccess(); 56 directAccess(); 57 58 System.out.println(); 59 60 System.out.println("分配性能测试:"); 61 bufferAllocate(); 62 directAllocate(); 63 } 64 } 65 66 //结果 67 68 访问性能测试: 69 access_nondirect:160 70 access_direct:135 71 72 分配性能测试: 73 allocate_nondirect:231 74 allocate_direct:644
3、Unsafe类
直接内存分配(allocateDirect)其实就是调用了sun.misc.Unsafe类来进行内存分配,Unsafe是sun.*API中的类,它不是J2SE中真正的一部份。
关于JVM对内存分配、直接内存分配、内存映射文件的一个测试示例:
(2684862条记录,每条记录包含4个long值,所有记录以二进制形式存储在文件中)
以上述三种方式读取每条记录(每种方式都是一次就分配足够的内存,直接内存分配、JVM内存分配、内存映射文件三者分别用时35ms、46ms、22ms):
1 package buaa.act.ucar.imtg.main; 2 3 import java.io.IOException; 4 import java.io.RandomAccessFile; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.FileChannel; 7 import java.nio.channels.FileChannel.MapMode; 8 9 /** 10 * @author zsm 11 * @date 2017年3月3日 上午10:23:53 12 */ 13 public class Test { 14 public static void main(String[] args) 15 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { 16 long startTime, dataCount; 17 18 try { 19 startTime = System.currentTimeMillis(); 20 System.out.println("reading"); 21 dataCount = readFromMMFile("F:/gps data/2016-11-11 18087 60399647/beijing_0900-1500_2684862.binary"); 22 System.out.printf("reading %d data,time used:%d ms ", dataCount, 23 (System.currentTimeMillis() - startTime)); 24 } catch (IOException e) { 25 // TODO Auto-generated catch block 26 e.printStackTrace(); 27 } 28 29 } 30 31 public static long readFromFile(String srcFilePath) throws IOException { 32 33 RandomAccessFile randomAccessFileOutput = new RandomAccessFile(srcFilePath, "rw"); 34 FileChannel inChannel = randomAccessFileOutput.getChannel(); 35 36 long devsn, gpstime; 37 double longitude, latitude; 38 long dataCount = 0; 39 40 ByteBuffer byteBuffer = ByteBuffer.allocateDirect((int) randomAccessFileOutput.length());// 35ms 41 // ByteBuffer byteBuffer = ByteBuffer.allocate((int) randomAccessFileOutput.length());// 46ms 42 while (inChannel.read(byteBuffer) > 0) { 43 byteBuffer.flip();// 进入read模式 44 while (byteBuffer.hasRemaining()) { 45 devsn = byteBuffer.getLong(); 46 gpstime = byteBuffer.getLong(); 47 longitude = Double.longBitsToDouble(byteBuffer.getLong()); 48 latitude = Double.longBitsToDouble(byteBuffer.getLong()); 49 // System.out.println(devsn + " " + gpstime + " " + longitude + " " + latitude); 50 dataCount++; 51 } 52 byteBuffer.clear();// 进入write模式 53 } 54 inChannel.close(); 55 randomAccessFileOutput.close(); 56 return dataCount; 57 } 58 59 // 22ms 60 public static long readFromMMFile(String srcFilePath) throws IOException { 61 RandomAccessFile randomAccessFileOutput = new RandomAccessFile(srcFilePath, "rw"); 62 FileChannel inChannel = randomAccessFileOutput.getChannel(); 63 64 long devsn, gpstime; 65 double longitude, latitude; 66 long dataCount = 0; 67 ByteBuffer byteBuffer = inChannel.map(MapMode.READ_ONLY, 0, randomAccessFileOutput.length()); 68 while (byteBuffer.hasRemaining()) { 69 devsn = byteBuffer.getLong(); 70 gpstime = byteBuffer.getLong(); 71 longitude = Double.longBitsToDouble(byteBuffer.getLong()); 72 latitude = Double.longBitsToDouble(byteBuffer.getLong()); 73 // System.out.println(devsn + " " + gpstime + " " + longitude + " " + latitude); 74 dataCount++; 75 } 76 inChannel.close(); 77 randomAccessFileOutput.close(); 78 return dataCount; 79 } 80 81 }