(k8s中)docker netty OOM问题记录
1、首先查看docker的内存占用情况:
docker top 容器名 -u 查看内存cpu占用率(容器名来自kubectl describe pod xxx或者docker ps)
可以看出内存一直增长,作为IO代理这是不正常的。
2、修改启动参数和配置文件
需要注意的是为了安全考虑,docker默认是不能使用一些调试手段的,需要修改启动参数和yaml
docker file中增加启动参数(yaml中应该也可以):
ENTRYPOINT ["java", "-jar", "/usr/local/bin/access-1.0-SNAPSHOT.jar", "-XX:NativeMemoryTracking=detail"]
yaml增加:
apiVersion: apps/v1
kind: Deployment
.......
spec:
........
template:
........
spec:
containers:
- name: access
.........
securityContext:
capabilities:
add: ["SYS_PTRACE"]
3、查看具体内存占用
各类内存增长情况:
#建立内存基线
jcmd 1 VM.native_memory baseline
#与基线对比
jcmd 1 VM.native_memory summary.diff
其中1是pid,可以通过jps查看
查看当前内存具体申请源:
jcmd 1 VM.native_memory detail scale=MB
查看结果是Other的内存增长比较明显:
[0x00007f76b2143b77] Unsafe_AllocateMemory0+0x87
[0x00007f769577c4ba]
(malloc=732MB type=Other #228)
Unsafe_AllocateMemory0一般是ByteBuf申请的内存,jvm不管理,也就是不会gc,需要自己关注申请和释放
4、自己申请的ByteBuf
自己申请的ByteBuf要么往下传递(通过ctx write或者fire read)由后面的pipe节点释放,要么自己通过release释放。
如果是写入服务器response中的content,通过ctx.writeAndFlush往下传递,由netty去管就行了。
5、在pipe的handle中收到的msg
比如服务器上收到的request,如果不是最后一个节点,则必须显式传递:ctx.fireChannelRead(msg);
如果是最后一个节点则自己手动释放,可以content.release也可以ReferenceCountUtil.release(msg);
也可以继承SimpleChannelInboundHandler,SimpleChannelInboundHandler中会释放,不用自己释放了。
如果是服务器处理一个请求回复一个响应,一般是作为最后一个节点,可以继承SimpleChannelInboundHandler。
6、netty的ByteBuf泄漏检测
在intellij中测试,先增加vm参数-Dio.netty.leakDetection.level=paranoid -Dio.netty.leakDetection.samplingInterval=128(需要再Modify options中选择才会出现,注意不是Program arguments):
测试代码:
public static void main(String[] args) {
ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(1024);
buf = null;
System.gc();
try {
Thread.sleep(1000);
// 再申请一次,此时会检测到泄漏并报告
PooledByteBufAllocator.DEFAULT.buffer(1024*1024);
} catch (Exception e) {
return;
}
}
运行后会报告泄露:
对于ServerHandler未回收的情况,如:
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest req) {
String respMsg = "Test Message Echo";
ByteBuf content = Unpooled.wrappedBuffer(respMsg.getBytes(CharsetUtil.UTF_8));
FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
resp.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
ctx.writeAndFlush(resp);
System.gc();
//在返回前msg没有显式回收,也没有交给下一个channel
return;
}
}
}
执行两次请求后会报告内存泄漏: