美文网首页
使用Netty模拟发生OOM

使用Netty模拟发生OOM

作者: 赵信信官属 | 来源:发表于2020-11-21 00:24 被阅读0次

我们模拟这么一个场景,客户端和服务端都使用Netty进行通信,客户端无限循环地向服务端发送数据,过了一会客户端就会出现OOM,我们分析OOM产生的原因,给我们排查线上问题提供一个思路和角度.

以下所有的分析都是基于以上描述的场景

本文适合对Netty要有一定的基础
代码放在了github上

image.png

设置的客户端虚拟机参数

-XX:MetaspaceSize=18M 
-XX:MaxMetaspaceSize=18M  
-XX:+HeapDumpOnOutOfMemoryError  
-XX:HeapDumpPath=D:\heapdump.hprof 
-Xmx1000M 
-XX:+PrintGC  
-XX:+PrintGCDetails 

为了讲解方便,我把一些主要代码粘贴如下
客户端代码

EventLoopGroup group = new NioEventLoopGroup();
EventLoopGroup businessGroup = new NioEventLoopGroup(8);
Bootstrap bootstrap = new Bootstrap();

bootstrap.group(group)
        .channel(NioSocketChannel.class)
        .handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) {
                ChannelPipeline channelPipeline = ch.pipeline();
                channelPipeline.addLast(new StringDecoder());// Netty自带的字符串解码器
                channelPipeline.addLast(new StringEncoder());// Netty自带的字符串编码器
                channelPipeline.addLast(businessGroup, new ClientHandler());// 自定义处理器
            }
        });

---

public class ClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // Channel激活之后,便无限循环地向服务端发送数据
        int i = 0;
        for (;;) {
            ctx.writeAndFlush("这个是客户端发送的第" + ++i + "个消息");
        }
    }
}

由于服务端只是接收数据,没有特殊地方,这里就不粘贴代码了.

先启动服务端,在启动客户端.
客户端就会连接服务端,通道建立之后,业务线程就会无限循环地向服务端发送数据.
你也可以通过JDK自带的工具观察内存的变化.

image.png

当程序运行一会之后,就会出现OOM异常

image.png

我们这里通过MAT工具分析下堆空间信息

image.png

导入文件.(至于怎么使用MAT工具这里不做介绍)

image.png image.png image.png

我们会发现taskQueue中有非常多的Task,这是因为向对端写数据的操作必须是IO线程来完成,业务线程只能把它的需求封装成一个Task放在IO线程的任务队列中.

image.png
// 源码位置: io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object, boolean, io.netty.channel.ChannelPromise)

private void write(Object msg, boolean flush, ChannelPromise promise) {
        
    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()) {// 判断当前线程是否是IO线程
        if (flush) {
            next.invokeWriteAndFlush(m, promise);
        } else {
            next.invokeWrite(m, promise);
        }
    } else {
        final AbstractWriteTask task;
        if (flush) {
            // 由于当前线程不是IO线程,所以只能封装成一个Task,放入到队列中
            task = WriteAndFlushTask.newInstance(next, m, promise);
        }  else {
            task = WriteTask.newInstance(next, m, promise);
        }
        // Task​放入到队列
        if (!safeExecute(executor, task, promise, m)) {
          
            task.cancel();
        }
    }        
}

由于业务线程是无限循环地写入数据,导致队列中的Task一直增多,最后导致OOM
一方面可能是服务端处理的比较慢,导致服务端TCP缓冲区满了,那么客户端的TCP缓冲区也会被写满,Netty就不能成功的写入TCP缓冲区,那么数据只能放在队列中,最后导致OOM.(当然我们这里不是因为这个原因,我们的服务端只是接收数据,没有任何业务耗时操作)
也有可能是网络等原因,导致客户端IO线程发送的比较慢(业务线程生成的数据比较快).
或者也有其他的原因.

Netty给我们提供了高低水位机制,当我们业务线程向Netty写入的数据过多的时候,一旦达到了高水位值(这个值我们可以设置),Netty就会设置Channel不可能.但是这里注意了,这里只是设置成不可能,我们还是依然可以向Netty中写入数据.但是如果我们忽略它,有可能造成上面这种OOM情况.

因此我们可以基于Netty提供的这种机制,控制我们的业务线程向Netty写入数据的速率.如果达到了高水位值,我们就暂时不要向Netty中写入数据,也就不会导致OOM发生.

我们改写客户端代码

public class ClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        // 设置高水位值(当然不一定非要在此处设置)
        ctx.channel().config().setWriteBufferHighWaterMark(20 * 1024 * 1024);

        int i = 0;

        for (;;) {
            // 通道可写
            if (ctx.channel().isWritable()) {
                ctx.writeAndFlush("这个是客户端发送的第" + ++i + "个消息");
            } else {// 通道不可写
                System.out.println("达到高水位,暂时不可写");
            }
            
        }
    }
}

以上代码也只是作为一个思路.

公众号 Netty历险记

相关文章

  • 使用Netty模拟发生OOM

    我们模拟这么一个场景,客户端和服务端都使用Netty进行通信,客户端无限循环地向服务端发送数据,过了一会客户端就会...

  • Netty模拟OOM-Metaspace

    在模拟OOM之前, 先简单说下Netty服务端向客户端发送数据的时候, 涉及两个存储数据的地方, 如下图所示 业务...

  • 人工制造OOM问题并生成heapdump文件

    OOM,即out of memory异常。实际上,不仅java虚拟机堆会发生OOM,栈也会发生OOM,本文只讨论如...

  • OOM问题排查方法

    根据日志确定发生OOM的原因和区域,以下几个内存区域都可能发生OOM,先找到打印出的OOM错误日志和dump文件(...

  • Netty学习--传输

    传输迁移 未使用Netty 的阻塞网络编程 未使用Netty 的异步网络编程 使用Netty 的阻塞网络处理 使用...

  • JAVA-每日一面 2022-01-24

    说说Netty 线程模型和 Netty 的零拷贝 Netty 线程模型首先,Netty 使用 EventLoop ...

  • netty Server端启动源码分析

    开篇 我们使用netty源码包netty-example中的EchoServer来分析使用netty作为网络通信框...

  • 传统IO,NIO,netty的几种实现方式

    1.未使用 Netty 的阻塞网络编程 2.未使用 Netty 的异步网络编程 3.使用 Netty 的阻塞网络处...

  • (一)初识Netty

    一、认识Netty之前 二、Netty是什么 三、Netty的使用场景

  • Android应用性能优化和性能分析

    Memory 分析和优化 有OOM发生但是没有现场保留 结合logcat和代码分析造成oom的泄漏点, 一般OOM...

网友评论

      本文标题:使用Netty模拟发生OOM

      本文链接:https://www.haomeiwen.com/subject/wyzjiktx.html