Java直接内存与堆内存
NIO的Buffer提供了一个可以不经过JVM内存直接访问系统物理内存的类——DirectBuffer。 DirectBuffer类继承自ByteBuffer,但和普通的ByteBuffer不同,普通的ByteBuffer仍在JVM堆上分配内存,其最大内存受到最大堆内存的限制;而DirectBuffer直接分配在物理内存中,并不占用堆空间,其可申请的最大内存受操作系统限制。
直接内存的读写操作比普通Buffer快,但它的创建、销毁比普通Buffer慢(猜测原因是DirectBuffer需向OS申请内存涉及到用户态内核态切换,而后者则直接从堆内存划内存即可)。
因此直接内存使用于需要大内存空间且频繁访问的场合,不适用于频繁申请释放内存的场合。
(Note:DirectBuffer并没有真正向OS申请分配内存,其最终还是通过调用Unsafe的allocateMemory()来进行内存分配。不过JVM对Direct Memory可申请的大小也有限制,可用-XX:MaxDirectMemorySize=1M设置,这部分内存不受JVM垃圾回收管理。)
使用对外内存的原因:
- 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。
- 提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。
以下是一些测试:
代码:
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 }
结果:
访问性能测试: access_nondirect:157 access_direct:134 分配性能测试: allocate_nondirect:231 allocate_direct:613
可见与在JVM堆分配内存(allocate)相比,直接内存分配(allocateDirect)的访问性能更好,但分配较慢。(一般如此,当然数据量小的话差别不是那么明显)