netty之内存泄露检测

写在前面

本文看下netty内存泄露检测相关内容,当然,这里的内存泄露不是bytebuf对象本身,是bytebuf关联的堆外内存。

1:实战

我们还是使用netty源码的example模块的echo例子,但是我们需要对server的handler稍微做些改造,使得能够出现内存泄露的情况,定义如下的handler:

@Sharable
public class EchoServerForDebugResourceLeakDetectorHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
//        System.out.println("echo server handler执行了!!!");
        ByteBuf buffer = ctx.alloc().buffer();
//        System.out.println("....................((((((((((((((((((((((");
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

主要是ByteBuf buffer = ctx.alloc().buffer();,只进行了申请,而没有调用buffer的release方法来释放内存,接着定义一个新的echo server使用新的会造成内存泄露的handler:

public final class EchoForDebugResourceLeakDetectorServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.,这里的线程数就设置为1了,正常如果只监听一个端口号只需要一个
        // ,而且源码也是只会选择一个EventLoop
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
//        final EchoServerHandler serverHandler = new EchoServerHandler();
        final EchoServerForDebugResourceLeakDetectorHandler serverHandler
                = new EchoServerForDebugResourceLeakDetectorHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            if (sslCtx != null) {
                                p.addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            //p.addLast(new LoggingHandler(LogLevel.INFO));
                            p.addLast(serverHandler);
                        }
                    });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

到这里还不行,需要启用error级别的日志,当然默认都是开启的,可以不用特别关注,另外就是要修改泄露检测级别为paranoid,从而每次都检测,方便我们复现问题,配置为-Dio.netty.leakDetection.level=PARANOID,当然,线上不需要特殊配置,使用默认的即可。

接着,首先启动server,再启动echo client,等一会就可以观察到server输出如下的信息:

s11:11:56.282 [nioEventLoopGroup-3-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records: 
Created at:
	io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:385)
	io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:187)
	io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:173)
	io.netty.buffer.AbstractByteBufAllocator.buffer(AbstractByteBufAllocator.java:107)
	io.netty.example.echo.EchoServerForDebugResourceLeakDetectorHandler.channelRead(EchoServerForDebugResourceLeakDetectorHandler.java:32)
	io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:382)
	...

这样子就复现内存泄露的场景了。

2:源码分析

当执行代码ByteBuf buffer = ctx.alloc().buffer();,会创建一个弱引用类ResourceLeakDetector,首先让弱引用类引用buffer,并将弱引用添加到list allLeaks中,也就是如下代码:

// io.netty.util.ResourceLeakDetector.DefaultResourceLeak#DefaultResourceLeak
DefaultResourceLeak(
        Object referent,
        ReferenceQueue<Object> refQueue,
        Set<DefaultResourceLeak<?>> allLeaks) {
    // 弱引用指向referent,一般是bytebuffer
    super(referent, refQueue);
    // ...
    // 添加弱引用到list中,用于辅助判断release方法是否被调用了
    allLeaks.add(this);
    // ...
}

同时netty会捎带手的调用如下方法:

// io.netty.util.ResourceLeakDetector#track
public final ResourceLeakTracker<T> track(T obj) {
    return track0(obj);
}

继续:

// io.netty.util.ResourceLeakDetector#track0
private DefaultResourceLeak track0(T obj) {
    Level level = ResourceLeakDetector.level;
    if (level == Level.DISABLED) {
        return null;
    }
    // 如果检测级别是PARANOID,则无脑检测,否则基于随机数的方式来做概率性的检测
    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);
}

主要看方法reportLeak();:

// io.netty.util.ResourceLeakDetector#reportLeak
private void reportLeak() {
    // 禁用error日志级别时,因为内存泄露信息时通过error级别日志输出的,所以必须开3启,当然一般我们也不会guan
    if (!needReport()) {
        // 清空refQueue
        clearRefQueue();
        return;
    }

    // Detect and report previous leaks.
    for (;;) {
        // 当发生了GC,弱引用对象会被放到refqueue中,这是弱引用的特性,即jdk提供的功能
        DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
        // 没有任何弱引用,说明还没有发生过GC
        if (ref == null) {
            break;
        }
        // dispose方法内部会执行allLeaks.remove(this),因为此时弱引用是通过refqueue获取到的,所以,其对应的buffer已经无用,且被GC了
        // ,如果是在allLeaks中包含的话,则说明是没有调用buffer.release()方法,也就说说明发生了内存泄漏
        if (!ref.dispose()) {
            continue;
        }
        // 从弱引用中获取堆栈信息,生成error日志,报告内存泄露信息,像这种:
        /**
         * 10:05:57.330 [nioEventLoopGroup-3-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information.
         * Recent access records:
         * Created at:
         * 	io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:385)
         * 	...
         */
        String records = ref.toString();
        if (reportedLeaks.add(records)) {
            if (records.isEmpty()) {
                reportUntracedLeak(resourceType);
            } else {
                // 日志输出record,即内存泄露的记录信息
                reportTracedLeak(resourceType, records);
            }
        }
    }
}

方法reportTracedLeak(resourceType, records);

// io.netty.util.ResourceLeakDetector#reportTracedLeak
protected void reportTracedLeak(String resourceType, String records) {
    logger.error(
            "LEAK: {}.release() was not called before it's garbage-collected. " +
                    "See https://netty.io/wiki/reference-counted-objects.html for more information.{}",
            resourceType, records);
}

相信你看到这里就眼前一亮了:
在这里插入图片描述

最后看个图来串联下:
在这里插入图片描述

核心就是,调用alloc方法申请内存时会创建弱引用ResourceLeakDetector并add到allLeaks,并且ResourceLeakDetector指向bytebuffer,如下图:
在这里插入图片描述
当强引用断开,发生GC的话,弱引用detector会被添加到refQueue中,bytebuf对象也会被回收:
在这里插入图片描述
如果是调用了release方法的话,则堆外内存释放,且allLeaks中的弱引用也会同步删除掉,否则不会删除,此时,当检测程序从refQueue中获取到弱引用对象后,发现在allLeaks中也有该弱引用,则就间接说明release方法没有被调用,也就发生堆外内存泄露了。绕死!!!

写在后面

参考文章列表

java的强,软,弱,虚引用介绍以及应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/919573.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务器上部署并启动 Go 语言框架 **GoZero** 的项目

要在服务器上部署并启动 Go 语言框架 **GoZero** 的项目&#xff0c;下面是一步步的操作指南&#xff1a; ### 1. 安装 Go 语言环境 首先&#xff0c;确保你的服务器上已安装 Go 语言。如果还没有安装&#xff0c;可以通过以下步骤进行安装&#xff1a; #### 1.1 安装 Go 语…

如何去掉el-input 中 type=“number“两侧的上下按键

<el-input v-model.trim"row.length" type"number" min"0" placeholder""></el-input> // 如何去掉el-input-number两侧的上下按键 ::v-deep input::-webkit-outer-spin-button, ::v-deep input::-webkit-inner-spin-butt…

前端注册代码

代码 <template><el-card class"register" style"max-width: 480px ; background-color: aliceblue;"><template #header><div class"card-header"><span>注册</span></div></template><el…

【第六课】Rust所有权系统(二)

目录 前言 借用和引用 借用规则 切片和迭代器 总结 前言 上节课介绍了Rust中的所有权系统&#xff0c;简单回顾一下&#xff0c;rust的内存系统系统&#xff0c;每一块内存都有一个主人&#xff0c;主人对这块内存有着读写和释放的权限&#xff0c;当主人离开作用域之后&am…

1024程序员节:永无bug

引言 每年的10月24日是程序员节。这一天不仅是程序员们的节日&#xff0c;更是对整个行业的庆祝与思考。在这个特殊的日子里&#xff0c;我们不仅回顾过去一年的成就与挑战&#xff0c;也展望未来的发展与机遇。本篇文章将围绕程序员节的主题&#xff0c;探讨前端技术的最新动…

STM32设计学生宿舍监测控制系统-分享

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 本项目旨在利用STM32单片机为核心&#xff0c;结合传感器技术、无线通信技…

Node.js | Yarn下载安装与环境配置

一、安装Node.js Yarn 是 Node.js 下的包管理工具&#xff0c;因此想要使用 Yarn 就必须先下载 Node.js。 推荐参考&#xff1a;Node.js | npm下载安装及环境配置教程 二、Yarn安装 打开cmd&#xff0c;输入以下命令&#xff1a; npm install -g yarn检查是否安装成功&…

【MySQL】MySQL在Centos环境安装

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; MySQL 目录 &#x1f308;前言&#x1f525;卸载不要的环境&#x1f525;检查系统安装包&#x1f525;卸载这些默认安装包&#x1f525;获取mysql官方yum源&#x1f525;安装mysql yum源…

selenium元素定位校验以及遇到的元素操作问题记录

页面元素定位方法及校验 使用比较多的是通过id、class和xpath来对元素进行定位。在定位前可以现在浏览器验证是否可以找到指定的元素。这样就不用每添加一个元素定位都运行代码来检查定位方式表达式是否正确。 使用XPATH定位 在浏览器F12&#xff0c;找到元素&#xff0c;在元…

LLM文档对话 —— pdf解析关键问题

一、为什么需要进行pdf解析&#xff1f; 最近在探索ChatPDF和ChatDoc等方案的思路&#xff0c;也就是用LLM实现文档助手。在此记录一些难题和解决方案&#xff0c;首先讲解主要思想&#xff0c;其次以问题回答的形式展开。 二、为什么需要对pdf进行解析&#xff1f; 当利用L…

小试牛刀-Anchor安装和基础测试

目录 一、编写目的 二、安装步骤 2.1 安装Rust 设置rustup镜像 安装Rust 2.2 安装node.js 2.3 安装Solana-CLI 2.4 安装Anchor CLI 三、Program测试 四、可能出现的问题 Welcome to Code Blocks blog 本篇文章主要介绍了 [Anchor安装和基础测试] 博主广交技术好友&…

Ubuntu 的 ROS 操作系统 turtlebot3 导航仿真

引言 导航仿真是机器人自动化系统中不可或缺的一部分&#xff0c;能够帮助开发者在虚拟环境中测试机器人在复杂场景下的运动与路径规划。 在 Gazebo 仿真环境中&#xff0c;TurtleBot3 配合 ROS 操作系统提供了强大的导航功能。在进行导航仿真时&#xff0c;首先需要准备地图&…

基于Java Springboot网络相册系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

AI 使用心态大转变:如何让 AI 成为日常工具

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

项目-摄像

树莓派摄像头使用方法 Camera教程 https://www.raspi.cc/index.php?cread&id53&page1 nanopc-t4 ​https://www.raspi.cc/index.php?cread&id53&page1 摄像头型号 Raspberry Pi Camera Rev 1.3 检测故障 dmesg | grep -i mipi piNanoPC-T4:~$ dmesg | …

基于SSM的农家乐管理系统+论文示例参考

1.项目介绍 功能模块&#xff1a;管理员&#xff08;农家乐管理、美食信息管理、住宿信息管理、活动信息、用户管理、活动报名、论坛等&#xff09;&#xff0c;普通用户&#xff08;注册登录、活动报名、客房预订、用户评价、收藏管理、模拟支付等&#xff09;技术选型&#…

RabbitMQ消息可靠性保证机制4--消费端限流

7.7 消费端限流 在类似如秒杀活动中&#xff0c;一开始会有大量并发写请求到达服务端&#xff0c;城机对消息进行削峰处理&#xff0c;如何做&#xff1f; 当消息投递的速度远快于消费的速度时&#xff0c;随着时间积累就会出现“消息积压”。消息中间件本身是具备一定的缓冲…

Orcad 输出有链接属性的PDF

安装adobe pdf安装Ghostscript修改C:\Cadence\SPB_16.6\tools\capture\tclscripts\capUtils\capPdfUtil.tcl ​ 设置默认打印机为 Adobe PDF ​ 将Ghostscript的路径修改正确 打开cadence Orcad &#xff0c;accessories->candece Tcl/Tk Utilities-> Utilities->PD…

android:taskAffinity 对Activity退出时跳转的影响

android:taskAffinity 对Activity跳转的影响 概述taskAffinity 的工作机制taskAffinity对 Activity 跳转的影响一个实际的开发问题总结参考 概述 在 Android 开发中&#xff0c;任务栈&#xff08;Task&#xff09;是一个核心概念。它决定了应用程序的 Activity 如何相互交互以…

Golang | Leetcode Golang题解之第565题数组嵌套

题目&#xff1a; 题解&#xff1a; func arrayNesting(nums []int) (ans int) {n : len(nums)for i : range nums {cnt : 0for nums[i] < n {i, nums[i] nums[i], ncnt}if cnt > ans {ans cnt}}return }