direct buffer 在堆外,分配贵、回收靠 GC,常被池化复用。这篇用 BufferPoolMXBean、基准和 Netty 的池化分配器,逐项量出它的内存位置、分配成本、池化的收益,以及 MaxDirectMemorySize 与 DisableExplicitGC 的冲突。
起始:count=0,capacity=0 MB,used=0 MB 分配 100 个 1 MB 的 direct buffer 并持有:count=100,capacity=100 MB,used=100 MB (这些 buffer 在 Java 堆里的占用很小:堆占用约 3 MB) 丢掉所有引用,但还没有 GC:count=100,capacity=100 MB,used=100 MB GC 之后:count=0,capacity=0 MB,used=0 MB allocateDirect 得到的内容全是 0:true;isDirect = true,hasArray = false,order = BIG_ENDIAN slice() 与原 buffer 共享同一块内存,不增加 direct 计数:count=1,capacity=1 MB,used=1 MB;slice.isDirect = true
大小 1024 字节:allocateDirect 241 ns/次;allocate(堆) 120 ns/次;自己写的池子借还 1 ns/次(30000 次,5 轮取最好) 大小 65536 字节:allocateDirect 3401 ns/次;allocate(堆) 1143 ns/次;自己写的池子借还 1 ns/次(30000 次,5 轮取最好) 大小 1048576 字节:allocateDirect 45249 ns/次;allocate(堆) 21712 ns/次;自己写的池子借还 1 ns/次(3000 次,5 轮取最好)
16 KB 的 direct 缓冲区 分配+写入+释放:Netty Pooled 52 ns/次;Netty Unpooled(release 立即释放)864 ns/次;JDK allocateDirect(靠 GC 回收)1640 ns/次 Netty 池的指标:usedDirectMemory = 4096 KB,numDirectArenas = 10,chunkSize = 4096 KB,tinyCacheSize/smallCacheSize/normalCacheSize = 256/64 持有 1000 个 16 KB 的池化 buffer:usedDirectMemory = 16 MB;JDK 的 BufferPoolMXBean(direct)= 117 MB 全部 release 之后:usedDirectMemory = 4 MB(池里仍然保留着内存);JDK direct 容量 = 105 MB 没有 release 的 ByteBuf 引用计数 refCnt = 1;release 一次后 refCnt = 0 对已释放的 ByteBuf 再写 → IllegalReferenceCountException: refCnt: 0
[-XX:MaxDirectMemorySize=64m,持有不放]
持有 1 MB 的 direct buffer,第 65 个时 → OutOfMemoryError: Cannot reserve 1048576 bytes of direct buffer memory (allocated: 67108864, limit: 67108864)
[-XX:MaxDirectMemorySize=64m,分配后立即丢弃]
分配 2000 个 1 MB 并立即丢弃:全部成功,耗时 123 ms
[同上,再加 -XX:+DisableExplicitGC]
分配并立即丢弃,第 65 个时 → OutOfMemoryError: Cannot reserve 1048576 bytes of direct buffer memory (allocated: 67108864, limit: 67108864)
[同上,再加 -Xlog:gc 看 GC 原因]
[0.005s][info][gc] Using G1
分配 2000 个 1 MB 并立即丢弃:全部成功,耗时 129 ms
-Xlog:gc 记录的 GC 31 次;原因统计: {'System.gc()': 31, 'G1 Evacuation Pause': 0, 'G1 Humongous Allocation': 0}