精华内容
下载资源
问答
  • Netty提供了ByteBuf泄露的检测机制。 JVM启动参数中添加: -Dio.netty.leakDetectionLevel=advanced, log4j2.xml配置io.netty日志记录即可。 禁用(DISABLED) – 完全禁止泄露检测,省点消耗。 简单(SIM...

    1. ByteBuf

     

    2. 问题描述

    日志记录中报堆外内存溢出。

     

    3. 问题定位及修改

    Netty提供了ByteBuf泄露的检测机制。

    JVM启动参数中添加: -Dio.netty.leakDetectionLevel=advanced , log4j2.xml配置io.netty日志记录即可。

    禁用(DISABLED) – 完全禁止泄露检测,省点消耗。
    简单(SIMPLE)   – 默认等级,告诉我们取样的1%的ByteBuf是否发生了泄露,但总共一次只打印一次,看不到就没有了。
    高级(ADVANCED) – 告诉我们取样的1%的ByteBuf发生泄露的地方。每种类型的泄漏(创建的地方与访问路径一致)只打印一次。对性能有影响。
    偏执(PARANOID) – 跟高级选项类似,但此选项检测所有ByteBuf,而不仅仅是取样的那1%。对性能有绝大的影响。

     

    检测到如下泄露点,

    举例1

     13:29:25.273 [MODBUS_MESSAGE_POOL-thread-14] [] [] [] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
    Created at:
        io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:604)
        io.netty.buffer.AbstractByteBuf.readBytes(AbstractByteBuf.java:849)
        com.pinnet.protocol.hwmodbus.message.ModbusMessageRunnable.run(ModbusMessageRunnable.java:46)
        java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        java.lang.Thread.run(Thread.java:748)
    

     可以看到readBytes()创建后,没有释放。

    修改:

    buffer.readBytes(4).release();

    举例2:

    2018-06-30 13:29:25.278 [MESSAGE_POOL-thread-14] [] [] [] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
        #1:
        io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:273)
        io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
        io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
        io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
        io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1380) io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1159) io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1194) io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) #2: io.netty.buffer.AdvancedLeakAwareByteBuf.getUnsignedShort(AdvancedLeakAwareByteBuf.java:172) io.netty.handler.codec.LengthFieldBasedFrameDecoder.getUnadjustedFrameLength(LengthFieldBasedFrameDecoder.java:469) io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:417) com.pinnet.protocol.hwmodbus.tcp.MobusLengthDecoder.decode(MobusLengthDecoder.java:32) #3: io.netty.buffer.AdvancedLeakAwareByteBuf.order(AdvancedLeakAwareByteBuf.java:70) io.netty.handler.codec.LengthFieldBasedFrameDecoder.getUnadjustedFrameLength(LengthFieldBasedFrameDecoder.java:462) io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:417) com.pinnet.protocol.hwmodbus.tcp.MobusLengthDecoder.decode(MobusLengthDecoder.java:32) io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:343) io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428) io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) #4: Hint: 'decoder' will handle the message from this point. #5: Hint: 'idleHandler' will handle the message from this point.

     

     修改:解码器中释放Bytebuf in

    public class MobusLengthDecoder extends LengthFieldBasedFrameDecoder {
    
        public MobusLengthDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) { super(byteOrder, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast); } @Override protected Object decode(ChannelHandlerContext ctx, io.netty.buffer.ByteBuf in) throws Exception { ByteBuf buf = (ByteBuf) super.decode(ctx, in); // LEAK: ByteBuf.release() was not called before it's garbage-collected.  in.release(); ... } }

     

    转载于:https://www.cnblogs.com/eaglediao/p/7004404.html

    展开全文
  • ByteBuf泄露检测原理 首先ByteBuf是一个java对象,Netty并不关注java对象的泄露,使用者作为java开发者必须保证没有发生java对象泄露,在这个前提下,Netty为ByteBuf包含的数据区域的泄露提供诊断。 java对象泄露,...

    我们现在知道ByteBuf是通过引用计数来管理生命周期的,换句话说,需要开发者手动管理,这对java程序员来说是非常有挑战性的一件事;为此,Netty提供了内存泄露检测机制。

    ByteBuf泄露检测原理

    首先ByteBuf是一个java对象,Netty并不关注java对象的泄露,使用者作为java开发者必须保证没有发生java对象泄露,在这个前提下,Netty为ByteBuf包含的数据区域的泄露提供诊断。

    java对象泄露,是指意外地缓存了不再使用对象的强引用,更多相关知识请自行搜索。

    假设开发者分配了一个ByteBuf,使用完之后,忘记调用ByteBuf.release,那么就发生泄露了;那么Netty怎么检测到这一事实呢?其实反而是依靠Java GC:假设ByteBuf这个java对象本身没有泄露,那么它最终会被java GC回收掉,如果一个ByteBuf对象被GC回收时,引用计数不为0,说明发生了泄漏

    Netty为了跟踪ByteBuf的使用轨迹(尤其是引用计数的变化),提供了一个特殊的包装器类LeakAwareByteBuf;为了检测ByteBuf被gc回收这个事件,则使用了“java弱引用”技术。

    LeakAwareByteBuf

    LeakAwareByteBuf是一种Wrapped ByteBuf,它有两个实现类SimpleLeakAwareByteBuf和AdvancedLeakAwareByteBuf,分别实现简单的和高级的泄露检测功能。

    SimpleLeakAwareByteBuf

    class SimpleLeakAwareByteBuf extends WrappedByteBuf {
    
    	 //要追踪的ByteBuf
    	 private final ByteBuf trackedByteBuf;
    	 
    	 //追踪信息的记录者
        final ResourceLeakTracker<ByteBuf> leak;
    
        //要追踪的ByteBuf和要包装的ByteBuf可以不是一个对象(主要发生在CompositeByteBuf场景下)
      	 //暂时可忽略这一点,认为wrapped=trackedByteBuf即可
        SimpleLeakAwareByteBuf(ByteBuf wrapped, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leak) {
            super(wrapped);
            this.trackedByteBuf = ObjectUtil.checkNotNull(trackedByteBuf, "trackedByteBuf");
            this.leak = ObjectUtil.checkNotNull(leak, "leak");
        }
    
        SimpleLeakAwareByteBuf(ByteBuf wrapped, ResourceLeakTracker<ByteBuf> leak) {
            this(wrapped, wrapped, leak);
        }
        
        ...
        
        
        //slice&duplicate等接口实现,同样返回一个具备泄露检测功能的副本
        @Override
        public ByteBuf slice() {
            return newSharedLeakAwareByteBuf(super.slice());
        }
        
        ...
        
        
        //touch接口的语义是记录ByteBuf的使用轨迹,但SimpleLeakAwareByteBuf内是一个空实现
        @Override
        public ByteBuf touch() {
            return this;
        }
    
        @Override
        public ByteBuf touch(Object hint) {
            return this;
        }
        
        ...
        
        
        //如果ByteBuf被顺利释放了,停止追踪它
        @Override
        public boolean release() {
            if (super.release()) {
                closeLeak();
                return true;
            }
            return false;
        }
    
        private void closeLeak() {
            boolean closed = leak.close(trackedByteBuf);
            assert closed;
        }
    }
    

    SimpleLeakAwareByteBuf通过一个叫做ResourceLeakTracker的对象来追踪ByteBuf的轨迹,不过它的touch方法是空的,所以SimpleLeakAwareByteBuf只可以追踪ByteBuf的创建和释放,并不能追踪使用过程。

    AdvancedLeakAwareByteBuf

    AdvancedLeakAwareByteBuf扩展了SimpleLeakAwareByteBuf的功能。

    final class AdvancedLeakAwareByteBuf extends SimpleLeakAwareByteBuf {
    
    
        static void recordLeakNonRefCountingOperation(ResourceLeakTracker<ByteBuf> leak) {
    		leak.record();
        }
        
       
        ...
        
        
        //slice这样的方法,自动记录了操作轨迹
        @Override
        public ByteBuf slice(int index, int length) {
            recordLeakNonRefCountingOperation(leak);
            return super.slice(index, length);
        }
        
        ...
        
        //基本的访问接口,自动记录了轨迹
        @Override
        public byte getByte(int index) {
            recordLeakNonRefCountingOperation(leak);
            return super.getByte(index);
        }
        
        ...
        
        
        //touch,让使用者可以主动记录轨迹
        @Override
        public ByteBuf touch() {
            leak.record();
            return this;
        }
    
        @Override
        public ByteBuf touch(Object hint) {
            leak.record(hint);
            return this;
        }
        
    }
    

    从以上代码可以看出,所谓“高级”检测,就是更详细地记录了ByteBuf的使用轨迹。它们都是通过ResourceLeakTracker记录信息,这个类后面会介绍。

    检测级别

    Netty为内存泄露定义了几种级别(ResourceLeakDetector.Level):

    • DISABLED,不检测
    • SIMPLE,简单采样检测,使用SimpleLeakAwareByteBuf
    • ADVANCED,高级采样检测,使用AdvancedLeakAwareByteBuf
    • PARANOID,偏执检测,全量使用AdvancedLeakAwareByteBuf

    采样是指,对新创建的ByteBuf有一定几率执行内存泄露检测。

    内存泄露检测对性能有明显影响,一般,线上最多采用SIMPLE级别,推荐DISABLED;测试环境可以使用ADVANCED,调试时才开启PARANOID级别。

    buf allocator

    现在看buf allocator是如何使用LeakAwareByteBuf的,以UnpooledByteBufAllocator为例:

    public class UnpooledByteBufAllocator {
    
    	 //Unpooled Heap buffer没有使用内存泄露检测
    	 @Override
        protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
            return PlatformDependent.hasUnsafe() ?
                    new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
                    new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
        }
    
    	 //Unpooled direct buffer通过toLeakAwareBuffer包装原始ByteBuf
        @Override
        protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
            final ByteBuf buf;
            if (PlatformDependent.hasUnsafe()) {
                buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) :
                        new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
            } else {
                buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
            }
            return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
        }
    }
    
    //toLeakAwareBuffer是定义在基类的一个工厂方法,将原始Bytebuf对象包装为具备内存泄露检测功能的LeakAwareByteBuf
    public class AbstractByteBufAllocator {
    	 protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) {
            ResourceLeakTracker<ByteBuf> leak;
            switch (ResourceLeakDetector.getLevel()) {
            	  //检测级别是Simple,
                case SIMPLE:
                    leak = AbstractByteBuf.leakDetector.track(buf);
                    if (leak != null) {
                        buf = new SimpleLeakAwareByteBuf(buf, leak);
                    }
                    break;
                case ADVANCED:
                case PARANOID:
                    leak = AbstractByteBuf.leakDetector.track(buf);
                    if (leak != null) {
                        buf = new AdvancedLeakAwareByteBuf(buf, leak);
                    }
                    break;
                default:
                    break;
            }
            return buf;
        }
    }
    

    ResourceLeakDetector.getLevel()是静态方法,说明内存泄露检测级别,是一个全局的设置。toLeakAwareBuffer依据level将buf包装为SimpleLeakAwareByteBuf或AdvancedLeakAwareByteBuf。而AbstractByteBuf.leakDetector.track方法创建ResourceLeakTracker对象来追踪buf,track方法实现了概率采样逻辑,在概率没有命中时,返回null。

    ResourceLeakTracker

    现在移步ResourceLeakTracker,看看它如何追踪ByteBuf是如何实现的。

    ResourceLeakTracker的接口抽象如下;

    public interface ResourceLeakTracker<T>  {
    
        //记录ByteBuf的使用轨迹
        void record();
        
        //记录轨迹,可以附加一个用户自定义提示信息
        void record(Object hint);
        
        //停止追踪ByteBuf
        boolean close(T trackedObject);
    }
    

    它的实现是ResourceLeakDetector的一个内部类,主要代码如下:

    //ResourceLeakDetector可认为以单例形式存在
    public class ResourceLeakDetector {
    		
    		//全局Leak对象集合
        	private final Set<DefaultResourceLeak<?>> allLeaks =
                Collections.newSetFromMap(new ConcurrentHashMap<DefaultResourceLeak<?>, Boolean>());
    
    		//弱引用队列
        	private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
    		
    		//注意关键点,它是WeakReference子类,以弱引用方式追踪目标ByteBuf对象
    		private staic class DefaultResourceLeak<T>
                extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
    
    		  //ByteBuf的每一个使用轨迹,是一个Record,多个Record形成一个链表
            private volatile Record head;
            
            //Record链表长度不可能无限制增长,在到达上限时,抛弃部分Record,这里记录抛弃的总数量
            private volatile int droppedRecords;
    
    		 //这是一个反向引用,指向ResourceLeakDetector.allLeaks,可以认为是进程内所有DefaultResourceLeak的集合
            private final Set<DefaultResourceLeak<?>> allLeaks;
            
            //对象的hash值
            private final int trackedHash;
    
    		  //referent指向目标ByteBuf
    		  //refQueue是全局弱引用队列(这部分知识请自行查阅)
    		  //allLeaks是全局的leak集合
            DefaultResourceLeak(
                    Object referent,
                    ReferenceQueue<Object> refQueue,
                    Set<DefaultResourceLeak<?>> allLeaks) {
                super(referent, refQueue);
    
                trackedHash = System.identityHashCode(referent);
                allLeaks.add(this);
                this.allLeaks = allLeaks;
            }
    
            @Override
            public void record() {
                record0(null);
            }
    
            @Override
            public void record(Object hint) {
                record0(hint);
            }
    
            private void record0(Object hint) {
    			  //这里创建Record节点,加入到链表
    			  //我们对链表是如何维护的没有兴趣,关键是Record到底记录了什么
            }
    
    		 //这是判定是否发生内存泄露发生的方法,如果this仍然在allLeaks集合中,那么说明发生内存泄露
    		 //该方法和close方法配合
            boolean dispose() {
                clear();
                return allLeaks.remove(this);
            }
    
    		 //LeakAwareByteBuf在引用计数为零时,调用close,说明ByteBuf成功释放,没有内存泄露
            @Override
            public boolean close() {
                if (allLeaks.remove(this)) {
                    clear();
                    headUpdater.set(this, null);
                    return true;
                }
                return false;
            }
    	}
    
    	 //这里最关键的一点:Recod继承自Throwable,可以记录当前的调用栈
        private static final class Record extends Throwable {
            Record(Record next, Object hint) {
                // This needs to be generated even if toString() is never called as it may change later on.
                hintString = hint instanceof ResourceLeakHint ? ((ResourceLeakHint) hint).toHintString() : hint.toString();
                this.next = next;
                this.pos = next.pos + 1;
            }
        }
    }
    

    报告内存泄露

    最后一个问题:内存泄露是怎么报告的,实现还是在ResourceLeakDetector里面。

    public class ResourceLeakDetector {
    
    	 //每次创建DefaultResourceLeak对象时,调用reportLeak来报告捕获的内存泄露
        private DefaultResourceLeak track0(T obj) {
            if (level.ordinal() < Level.PARANOID.ordinal()) {
                if ((PlatformDependent.threadLocalRandom().nextInt(samplingInterval)) == 0) {
                    reportLeak();
                    return new DefaultResourceLeak(obj, refQueue, allLeaks);
                }
                return null;
            }
            reportLeak();
            return new DefaultResourceLeak(obj, refQueue, allLeaks);
        }
        
        
        private void reportLeak() {
        	  //遍历弱引用队列
            for (;;) {
                DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
                if (ref == null) {
                    break;
                }
    
    			  //对于弱引用队列里的ref,它关联的ByteBuf对象被gc掉了
    			  //dispose方法查看这个ref是否仍然全局allLeaks里面,如果是,说明发生了内存泄露;
    			  //参照DefaultResourceLeak.close方法,buf的引用计数变零时,会从allLeaks移除
                if (!ref.dispose()) {
                    continue;
                }
    			
    			  //打印泄露日志
                String records = ref.toString();
                if (reportedLeaks.add(records)) {
                    if (records.isEmpty()) {
                        reportUntracedLeak(resourceType);
                    } else {
                        reportTracedLeak(resourceType, records);
                    }
                }
            }
        }
    }
    

    以上代码可以看出,在追踪一个新的Bytebuf时,检查并报告目前是否发生了泄露。

    什么类型的ByteBuf能检测泄露

    上面已经分析了Netty ByteBuf泄露检测的原理,底层是基于java GC和弱引用技术,实现层面通过将ByteBuf包装为LeakAwareByteBuf,来追踪它的使用轨迹。

    这个技术原理决定了不是所有的ByteBuf类型都能被检测泄露:

    • heap ByteBuf不行(或者说没有意义),因为heap ByteBuf的数据域是一个java byte数组,它的生命周期和ByteBuf完全一样,由GC完全负责;
    • pooled byteBuf不行,因为pooled ByteBuf使用完了之后就会被pool回收,压根不会被gc释放;

    实际上,只有unpooled direct ByteBuf适用这个检测机制,在项目测试阶段,为了检测是否有ByteBuf泄露,可以强制全部使用UnpooledByteBufAllocator(设置ChannelOption.ALLOCATOR=UnpooledByteBufAllocator.DEFAULT)(大部分平台默认开启direct内存模式)。

    展开全文
  • 今天程序跑了很久,突然发现日志打印内存泄漏,当时在想,这个程序功能很单一,为什么会内存泄漏呢,后来看代码,发现有ByteBuf,但还是很疑惑,我以为netty会自动回收,后来看了博客,有些人说业务中的ByteBuf需要...

    netty-server服务端在接收数据的时候会将数据转换为ByteBuf,然后对数据处理。今天程序跑了很久,突然发现日志打印内存泄漏,当时在想,这个程序功能很单一,为什么会内存泄漏呢,后来看代码,发现有ByteBuf,但还是很疑惑,我以为netty会自动回收,后来看了博客,有些人说业务中的ByteBuf需要我们自己去回收。

    代码片段:
    public void channelRead(ChannelHandlerContext ctx, Object msg){
    ByteBuf buf = null;
    try{
    buf = (ByteBuf)msg;
    //拿到数据

            byte[] barray = new byte[buf.readableBytes()];
            buf.getBytes(0,barray);
            String data = new String(barray);
    

    }

    日志:
    [ioEventLoop-6-1] io.netty.util.ResourceLeakDetector : LEAK: ByteBuf.release() was not called before it’s garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.

    解决办法:
    buf.release();

    展开全文
  • Netty bytebuf 内存泄漏

    千次阅读 2017-03-15 17:09:40
    客户端发送消息,一直处于高峰,每秒几千条。或曲线状态。此时客户端内存不断飙升 分析原因:客户端发送存在大量缓存,因服务端接收能力有限。 客户端注掉: bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR,new ...
    客户端发送消息,一直处于高峰,每秒几千条。或曲线状态。此时客户端内存不断飙升
    分析原因:客户端发送存在大量缓存,因服务端接收能力有限。
    客户端注掉:
    bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(Constants.RCVBUF_ALLOCATOR_MIN, Constants.RCVBUF_ALLOCATOR_INITIAL, Constants.RCVBUF_ALLOCATOR_MAX));
    服务端同样注掉RCVBUF_ALLOCATOR

    在一直高峰时依然内存不断上涨,但曲线状态,则较为理想。

    从客户端跑完之后,服务端还在一直跑接收消息来看,问题出在服务端处理能力有限,很多信息暂存服务端内存,继续接收客户端消息慢。
    展开全文
  • 吃透Netty源码系列五十之ByteBuf内存泄漏问题一最容易内存泄漏的例子客户端代码服务端代码堆内内存溢出虚拟机参数设置运行服务端还没开启客户端开启客户端visualvm看内存使用情况visualvm看内存对象数量visualvm看GC...
  • io.netty.util.ResourceLeakTracker ,内存泄露追踪器接口,上文知每个资源( 例如:ByteBuf 对象 ),会创建一个追踪它是否内存泄露的 ResourceLeakTracker 对象,接口方法定义: public interface ...
  • Netty channel看完看Buffer。...ByteBuf 是最值得注意的,它使用了引用计数来改进分配内存和释放内存的性能。–Netty官方文档翻译 引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存...
  • 一张图片简单明了说说清楚 netty 引用计数! 从 Netty4 开始,某些对象的生命周期由引用计数来管理,提高分配和回收的性能...本文将解释如何进行引用计数, 何时对引用对象进行销毁, 如何解决缓存泄漏 Leak 等问题?

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,211
精华内容 484
关键字:

bytebuf泄露