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