PlatformDependent類當中的常量,定義了允許使用的堆外內存最大值,該值可以通過-XX:MaxDirectMemorySize=2G設置。
private static final long MAX_DIRECT_MEMORY = maxDirectMemory0();
在netty中,如果使用了堆外內存,Netty會進行統計,如果超過最大限制會拋出異常。
private static void incrementMemoryCounter(int capacity) {
if (DIRECT_MEMORY_COUNTER != null) {
for (;;) {
long usedMemory = DIRECT_MEMORY_COUNTER.get();
long newUsedMemory = usedMemory + capacity;
if (newUsedMemory > DIRECT_MEMORY_LIMIT) {
throw new OutOfDirectMemoryError("failed to allocate " + capacity
+ " byte(s) of direct memory (used: " + usedMemory + ", max: " + DIRECT_MEMORY_LIMIT + ')');
}
if (DIRECT_MEMORY_COUNTER.compareAndSet(usedMemory, newUsedMemory)) {
break;
}
}
}
}
private static void decrementMemoryCounter(int capacity) {
if (DIRECT_MEMORY_COUNTER != null) {
long usedMemory = DIRECT_MEMORY_COUNTER.addAndGet(-capacity);
assert usedMemory >= 0;
}
}
我們在Eclipse-Run Configure中設置JVM參數 -XX:MaxDirectMemorySize=1K,稍後我們分配10K的空間,系統會報內存溢出錯誤。
public static void main(String[] args) throws Exception {
ByteBufAllocator allocator = UnpooledByteBufAllocator.DEFAULT;
ByteBuf buf = allocator.directBuffer(10240);
buf.release();
}
PlatformDependent的DIRECT_MEMORY_COUNTER常量實時記錄了Netty堆外內存的使用情況。
這個字段是私有的,我們可以使用反射拿到該指定進行打印或監控,寫一個程序,沒秒打印值。
public class DirectMemoryReporter {
private AtomicLong directMomery;
public DirectMemoryReporter() {
Field field = ReflectionUtils.findField(PlatformDependent.class, "DIRECT_MEMORY_COUNTER");
field.setAccessible(true);
try {
directMomery = (AtomicLong) field.get(PlatformDependent.class);
} catch (Exception e) {
}
GlobalEventExecutor.INSTANCE.scheduleAtFixedRate(this::doReport, 0, 1, TimeUnit.SECONDS);
}
public void doReport() {
System.out.println("netty_directMomery_log \uff1a " + directMomery.get());
}
}
首先我們使用非池化的UnpooledByteBufAllocator,創建2個ByteBuf對象,它們優先都會使用堆外內存,我們可以看到推外內存使用了200個字節。
public static void main(String[] args) throws Exception {
DirectMemoryReporter directMemoryReporter = new DirectMemoryReporter();
UnpooledByteBufAllocator allocator = UnpooledByteBufAllocator.DEFAULT;
ByteBuf buf1 = allocator.buffer(100);
ByteBuf buf2 = allocator.directBuffer(100);
System.out.println(buf1.getClass());
}
}
netty_directMomery_log \uff1a 0
class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf
netty_directMomery_log \uff1a 200
netty_directMomery_log \uff1a 200
netty_directMomery_log \uff1a 200
然後我們使用PooledByteBufAllocator測試,可以看到它首選分配了16M內存,然後若干個堆外ByteBuf對象,共享這16M內存,效率得到提升,也無需受到java-GC的干擾。
public class ByteBufDemo {
public static void main(String[] args) throws Exception {
DirectMemoryReporter directMemoryReporter = new DirectMemoryReporter();
PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
ByteBuf buf1 = allocator.buffer(100);
ByteBuf buf2 = allocator.directBuffer(100);
System.out.println(buf1.getClass());
}
}
netty_directMomery_log \uff1a 0
class io.netty.buffer.PooledUnsafeDirectByteBuf
netty_directMomery_log \uff1a 16777216
netty_directMomery_log \uff1a 16777216
netty_directMomery_log \uff1a 16777216