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