记netty一次踩坑过程

参考博客:
踩坑过程相同
上篇:http://www.cnblogs.com/rainy-shurun/p/5213086.html
下篇:http://www.cnblogs.com/yuyijq/p/4431798.html
堆外内存详解:
https://www.jianshu.com/p/50be08b54bee
堆外内存回收方法:
http://www.cnblogs.com/duanxz/p/6089485.html

基础概念:

Netty为了提高网络的吞吐量,在业务层与socket之间又增加了一个ChannelOutboundBuffer。在我们调用channel.write的时候,所有写出的数据其实并没有写到socket,而是先写到ChannelOutboundBuffer。当调用channel.flush的时候才真正的向socket写出。因为这中间有一个buffer,就存在速率匹配了,而且这个buffer还是无界的。也就是你如果没有控制channel.write的速度,会有大量的数据在这个buffer里堆积,而且如果碰到socket又『写不出』数据的时候,很有可能的结果就是资源耗尽。而且这里让这个事情更严重的是ChannelOutboundBuffer很多时候我们放到里面的是DirectByteBuffer,什么意思呢,意思是这些内存是放在GC Heap之外。如果我们仅仅是监控GC的话还监控不出来这个隐患.

CPU占用已经到100%了,而load也非常高的时候,cpu很有可能来不及处理网络事件,这个时候send buffer就有可能会堆满,这就导致socket写不出数据了。

相同问题排查 解决方案:

https://www.jianshu.com/p/13f72e0395c8

上文的现象可以描述为:

(1) 不断进行full GC,导致CPU打满,每次GC后old Gen的内存占用率仍然不下降。用free命令发现剩余物理内存总量非常低


(2) Socket无法发出包,使用wireshark抓包发现出现状态TCP windows update,说明缓冲区已满。
(3) 使用jmap命令:jmap -histo pid > histo.txt发现大量对象为:DefaultChannelPromise、WriteAndFlushTask DefaultHandle

(4) 进程挂掉,查看/var/log/message 日志,由于oom发现进程被操作系统干掉:

Aug 19 08:32:38 mybank-ant kernel: : [6176841.247990] Out of memory: Kill process 24673 (java) score 946 or sacrifice childAug 19 08:32:38 mybank-ant kernel: : [6176841.249016] Killed process 24673, UID 1801, (java) total-vm:5120504kB, anon-rss:3703788kB, file-rss:484kB

原因解析:

记netty一次踩坑过程_第1张图片

1.        WirteAndFlush的写入速度远远大于TCP发送缓冲区的消化速度,TCP缓冲区满。大量对象在Channelbuffer缓冲区内,其对象为(DefaultChannelPromise、WriteAndFlushTask、Recycler),这些创建的对象经过若干次young GC后进入老年代,由于尚在使用中,不能进行垃圾回收,导致full GC频繁。

2.        频繁Full GC导致cpu打到100%,CPU无法处理网络请求,从而导致虽然TCP缓冲区内有数据,但是数据无法发出。

3.        使用DirectByteBuffer使用堆外内存,在不断申请堆外内存后,导致物理内存占用率急剧增加,当物理内存不足时,开始吃缓存(swap),最后由于oom,被操作系统当做bad process 干掉。

if(channelHandlerContext.channel().isWritable()){
                channelHandlerContext.writeAndFlush(commandOut).addListener(future -> {
                    if (!future.isSuccess()) {
                        logger.warn("unexpected push. msg:{} fail:{}", msg, future.cause().getMessage());
                    }
                });
            }else{
                try {
                    channelHandlerContext.writeAndFlush(commandOut).sync();
                    logger.info("publish macdonaldMsg,sended. remoteAddress:[{}], msg:[{}]", channelHandlerContext.channel().remoteAddress(), msg);
                } catch (InterruptedException e) {
                    logger.info("write and flush msg exception. msg:[{}]",msg,e);
                }
            }




你可能感兴趣的:(深入理解JVM)