说明:针对线上发生的一次直接内存OOM,通过源码了解一下 Netty 的直接内存使用,主要看 Server 端。
一、环境
- 系统:macOS Monterey(MacBook Pro 13-inch,M1,2020)
- JDK:11
- Maven:3.8.1
- IDEA:2022.2.1(Ultimate Edition)
- Netty:netty-all 4.1.65.Final
二、工程
1、引入依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.65.Final</version>
</dependency>
2、创建Server、Client
创建4个类:NettyServer、NettyServerHandler、NettyClient、NettyClientHandler,Server、Client端的代码比较简单,Server端的 pipeline里添加了一个编码器、一个业务Handler。
pipeline.addLast(new StringEncoder());
pipeline.addLast(new NettyServerHandler());
三、源码分析
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
String msg = "12345678901234567890123456789012345678901234567890,";
Logger log = LoggerFactory.getLogger(NettyServerHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("连接建立...");
log.warn("发送数据...");
for (int i = 0; i < 10000; i++) {
ctx.writeAndFlush(msg); // 从这里开始debug
}
log.warn("发送数据结束...");
}
// ChannelHandlerContext 实现类是 AbstractChannelHandlerContext,
// 继续下一步到:AbstractChannelHandlerContext#writeAndFlush(Object)
@Override
public ChannelFuture writeAndFlush(Object msg) {
return writeAndFlush(msg, newPromise());
}
// 继续下一步:AbstractChannelHandlerContext#write(Object, boolean, ChannelPromise)
private void write(Object msg, boolean flush, ChannelPromise promise) {
ObjectUtil.checkNotNull(msg, "msg");
try {
if (isNotValidPromise(promise, true)) {
ReferenceCountUtil.release(msg);
// cancelled
return;
}
} catch (RuntimeException e) {
ReferenceCountUtil.release(msg);
throw e;
}
final AbstractChannelHandlerContext next = findContextOutbound(flush ?
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise); // flash为true,断点到这一行
} else {
next.invokeWrite(m, promise);
}
} else {
// 继续下一步
void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
if (invokeHandler()) {
invokeWrite0(msg, promise); // 断点到这一行
invokeFlush0();
} else {
writeAndFlush(msg, promise);
}
}
// 继续下一步
private void invokeWrite0(Object msg, ChannelPromise promise) {
try {
((ChannelOutboundHandler) handler()).write(this, msg, promise); // 断点到这一行
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
}
// 继续下一步 pipeline配置的第一个ChannelOutboundHandler是StringEncoder, 此处断点进入到 MessageToMessageEncoder#write
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
CodecOutputList out = null;
try {
if (acceptOutboundMessage(msg)) {
out = CodecOutputList.newInstance();
@SuppressWarnings("unchecked")
I cast = (I) msg;
try {
encode(ctx, cast, out); // 断点到这一行
} finally {
ReferenceCountUtil.release(cast);
}
// 继续下一步
@Override
protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
if (msg.length() == 0) {
return;
}
// ctx.alloc() 的值是:PooledByteBufAllocator(directByDefault: true)
out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset)); // 断点到这一行
}
// 继续下一步 ByteBufUtil.encodeString
public static ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset) {
return encodeString0(alloc, false, src, charset, 0);
}
// 继续下一步
static ByteBuf encodeString0(ByteBufAllocator alloc, boolean enforceHeap, CharBuffer src, Charset charset,
int extraCapacity) {
final CharsetEncoder encoder = CharsetUtil.encoder(charset);
int length = (int) ((double) src.remaining() * encoder.maxBytesPerChar()) + extraCapacity;
boolean release = true;
final ByteBuf dst;
if (enforceHeap) {
dst = alloc.heapBuffer(length);
} else {
// 断点到这一行。因为 enforceHeap 的值是 false,如果是 true,将分配堆内存,else 猜测是非堆内存(直接内存),继续往下验证...
dst = alloc.buffer(length);
}
// 继续下一步 AbstractByteBufAllocator#buffer(int)
@Override
public ByteBuf buffer(int initialCapacity) {
if (directByDefault) {
return directBuffer(initialCapacity); // 断点到这一行,
}
return heapBuffer(initialCapacity);
}
// 继续下一步
@Override
public ByteBuf directBuffer(int initialCapacity) {
return directBuffer(initialCapacity, DEFAULT_MAX_CAPACITY);
}
// 继续下一步
@Override
public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
if (initialCapacity == 0 && maxCapacity == 0) {
return emptyBuf;
}
validate(initialCapacity, maxCapacity);
return newDirectBuffer(initialCapacity, maxCapacity); // 断点到这一行,已经看到使用的是直接内存了
}
// 继续下一步 PooledByteBufAllocator#newDirectBuffer,为什么是这个实现?在上面 ctx.alloc() 中已得到,
@Override
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
PoolThreadCache cache = threadCache.get();
PoolArena<ByteBuffer> directArena = cache.directArena;
final ByteBuf buf;
if (directArena != null) {
// 断点到这一行,已基本明确是使用直接内存。
// 关于 directArena 对象,则是Netty内存分配的相关大小及数据等的划分,此篇只分析是否使用直接内存
buf = directArena.allocate(cache, initialCapacity, maxCapacity);
} else {
buf = PlatformDependent.hasUnsafe() ?
UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) :
new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
}
return toLeakAwareBuffer(buf);
}