java 10 支持了DIRECT IO,可以绕过page cache直接写文件。
1 2 Path path = Paths.get("test.txt" ); FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE, ExtendedOpenOption.DIRECT);
DIRECT IO需要对齐,但是他有一些非常微妙的地方。
那些地方需要对齐: 1 2 3 Util.checkChannelPositionAligned(position(), alignment); Util.checkBufferPositionAligned(bb, pos, alignment); Util.checkRemainingBufferSizeAligned(rem, alignment);
分别是:
文件写入位置
directBuffer起始地址
directBuffer中剩余数据的长度
他们都必须是alignment的整数倍。
按多少字节对齐? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 JNIEXPORT jint JNICALL Java_sun_nio_ch_FileDispatcherImpl_setDirect0 (JNIEnv *env, jclass clazz, jobject fdo) { jint fd = fdval (env, fdo); jint result; #ifdef MACOSX struct statvfs file_stat ; #else struct statvfs64 file_stat ; #endif #ifdef MACOSX result = fstatvfs (fd, &file_stat); #else result = fstatvfs64 (fd, &file_stat); #endif if (result == -1 ) { JNU_ThrowIOExceptionWithLastError (env, "DirectIO setup failed" ); return result; } else { result = (int )file_stat.f_frsize; } #else result == -1 ; #endif return result; }
statvfs.frsize是文件系统的逻辑块大小,可能包含多个物理块。
unsigned long f_frsize; /* Fragment size */
如何获得起始起始位置是对齐的directBuffer 系统分配的内存虚拟地址并不确定,需要分配大一些的空间(pagesize + capcity),只使用其中的一部分。
1 long size = Math.max(1L , (long )cap + (pa ? ps : 0 ));
假如设置了-Dsun.nio.PageAlignDirectMemory=true
参数会自动对齐。
注:这场jdk里的注释是错的,-XX:MaxDirectMemorySize=没有任何作用
这里DirectByteBuffer会把整段 分配的内存写0,而不仅仅是用到的部分。导致没用到的虚拟内存也会分配物理内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 long base = 0 ;try { base = UNSAFE.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } UNSAFE.setMemory(base, size, (byte ) 0 ); if (pa && (base % ps != 0 )) { address = base + ps - (base & (ps - 1 )); } else { address = base; }
假设分配一页内存,虚拟地址横跨两页,最终会导致分配两倍的物理内存 。如果分配小量内存或者开启大页情况可能会更严重。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Hello { static final int _4K = 4096 ; static Object[] save = new Object[256 * 1024 ]; public static void main (String[] args) { for (int i = 0 ; i < 256 * 1024 ; i++) { ByteBuffer buffer = ByteBuffer.allocateDirect(_4K); save[i] = buffer; } try { new CountDownLatch(1 ).await(); } catch (InterruptedException e) { e.printStackTrace(); } } }
这份代码不带对齐参数占用的物理内存约为1G,而加上-Dsun.nio.PageAlignDirectMemory=true
将占用2G。
1 2 3 4 5 6 7 $ java -XX:NativeMemoryTracking=summary -XX:MaxDirectMemorySize=1g -Dsun.nio.PageAlignDirectMemory=true Hello $ jcmd 29084 VM.native_memory 29084: Native Memory Tracking: Total: reserved=7840547KB, committed=2470423KB - Other (reserved=2097152KB, committed=2097152KB) (malloc=2097152KB #262144)
注意到-XX:MaxDirectMemorySize=1g
,但是DirectMemory远远超出了1g。
MaxDirectMemorySize的逻辑 Bits.tryReserveMemory是按分配的容量算的,而不是实际分配的内存大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private static boolean tryReserveMemory (long size, int cap) { long totalCap; while (cap <= MAX_MEMORY - (totalCap = TOTAL_CAPACITY.get())) { if (TOTAL_CAPACITY.compareAndSet(totalCap, totalCap + cap)) { RESERVED_MEMORY.addAndGet(size); COUNT.incrementAndGet(); return true ; } } return false ; }
而在开启对齐的时候size = cap + pageSize,显然size要比cap大得多。