【内存泄漏】数据库连接connectionPhantomRefs内存过大

news2024/11/26 2:47:34

1. 问题背景

线上出现内存报警,内存增长曲线如下
在这里插入图片描述
dump内存文件,临时重新发布服务。后经排查发现是数据库连接池设置不合理以及mysql-connector-java 5.1.49有内存泄漏bug。以下为对此问题的分析及问题总结。

1.1 应用背景

数据库连接池: HikariCP
mysql-connector-java : 5.1.49版本
HikariCP配置:
在这里插入图片描述

2.问题分析

2.1 dump分析

通过 MAT 工具分析发现,com.mysql.jdbc.NonRegisteringDriver$ConnectionPhantomReference 实例较多且无法回收 如下图:
在这里插入图片描述
然后看大对象列表,NonRegisteringDriver 对象确实占内存比较多,其中成员变量connectionPhantomRefs占内存最多,里面存的是数据库连接的虚引用,其类型是 ConcurrentHashMap<ConnectionPhantomReference, ConnectionPhantomReference>,占比最多

2.2 源码分析

mysql创建连接源码:
com.mysql.jdbc.NonRegisteringDriver#connect

 public Connection connect(String url, Properties info) throws SQLException {
        if (url != null) {
            if (StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:loadbalance://")) {
                return this.connectLoadBalanced(url, info);
            }

            if (StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:replication://")) {
                return this.connectReplicationConnection(url, info);
            }
        }

        Properties props = null;
        if ((props = this.parseURL(url, info)) == null) {
            return null;
        } else if (!"1".equals(props.getProperty("NUM_HOSTS"))) {
            return this.connectFailover(url, info);
        } else {
            try {
            // 这里创建连接
                com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);
                return newConn;
            } catch (SQLException var6) {
                throw var6;
            } catch (Exception var7) {
                SQLException sqlEx = SQLError.createSQLException(Messages.getString("NonRegisteringDriver.17") + var7.toString() + Messages.getString("NonRegisteringDriver.18"), "08001", (ExceptionInterceptor)null);
                sqlEx.initCause(var7);
                throw sqlEx;
            }
        }
    }

而 ConnectionImpl类在初始化构造的时候会调用NonRegisteringDriver.trackConnection(this);方法,而这个方法我们看命名就知道是追踪数据库连接的,我们接着往下看

public class NonRegisteringDriver implements Driver {

protected static final ConcurrentHashMap<ConnectionPhantomReference, ConnectionPhantomReference> connectionPhantomRefs = new ConcurrentHashMap();
protected static final ReferenceQueue<ConnectionImpl> refQueue = new ReferenceQueue();

  protected static void trackConnection(com.mysql.jdbc.Connection newConn) {
  		// 就是这里声明虚引用ConnectionPhantomReference,放入ReferenceQueue
        ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl)newConn, refQueue);
        connectionPhantomRefs.put(phantomRef, phantomRef);
    }
 }

第一行代码声明了一个名为connectionPhantomRefs的ConcurrentHashMap容器,该容器用于存储ConnectionPhantomReference实例。

第二个方法trackConnection的作用是将新连接添加到connectionPhantomRefs映射中。它接受一个com.mysql.jdbc.Connection对象作为参数,创建一个新的ConnectionPhantomReference实例,并使用它和引用队列(refQueue)将其添加到connectionPhantomRefs映射中。

总的来说,这两个代码片段旨在通过使用虚引用来实现跟踪连接到MySQL数据库的机制。虚引用用于跟踪已被JVM垃圾回收的对象,允许程序在对象从内存中删除后执行特定任务。

 static class ConnectionPhantomReference extends PhantomReference<ConnectionImpl> {
        private NetworkResources io;

        ConnectionPhantomReference(ConnectionImpl connectionImpl, ReferenceQueue<ConnectionImpl> q) {
            super(connectionImpl, q);

            try {
                this.io = connectionImpl.getIO().getNetworkResources();
            } catch (SQLException var4) {
            }

        }

        void cleanup() {
            if (this.io != null) {
                try {
                    this.io.forceClose();
                } finally {
                    this.io = null;
                }
            }

        }
    }

ConnectionPhantomReference后置处理如上,leanup() 方法用于在连接对象被垃圾回收后清理网络资源。它检查 io 属性是否为空,如果不为空,则调用 forceClose() 方法来强制关闭底层网络资源,最终将 io 属性设置为 null。整个过程确保连接对象被垃圾回收时,底层网络资源也被正确地释放。

MySQL为什么要使用虚引用来解决IO资源回收问题?

MySQL 使用虚引用来解决 IO 资源回收问题,主要是因为 JDBC 连接对象在关闭连接时无法保证其底层网络资源会被立即释放。这可能会导致底层网络资源长时间占用,最终导致应用程序出现性能下降或者资源耗尽的情况。

使用虚引用的好处在于,它允许程序在对象从内存中删除后执行特定任务。 MySQL 驱动程序利用 Java 提供的引用队列机制,将 JDBC 连接对象的虚引用加入到队列中。一旦连接对象被垃圾回收,JVM 会将它加入到引用队列中等待进一步处理。此时,MySQL 驱动程序通过监视引用队列并清理底层网络资源,确保这些资源在连接对象被垃圾回收时被正确地释放,从而避免了底层网络资源长时间占用的问题。

以下是虚引用的主要特点:

1.不影响对象的生命周期: 虚引用的存在并不会延长对象的生命周期。即使对象被虚引用引用着,只要没有其他强引用、软引用或者弱引用指向该对象,它也会被垃圾回收器回收。
2.用于跟踪对象的回收状态: 虚引用主要用于跟踪对象被回收的状态。当对象被垃圾回收器回收时,虚引用会被添加到与之关联的引用队列中,可以通过检查引用队列来得知对象已经被回收。
3.无法通过虚引用获取对象: 虚引用不可以直接通过 get() 方法获取到对象的引用,它的 get() 方法始终返回 null。因此,虚引用主要用于进行对象回收状态的跟踪,而无法用于获取对象的引用。

那MySQL是怎样执行最终的IO资源回收的呢,是使用了定时线程还是异步守护线程?
是使用异步守护线程处理的
在这里插入图片描述
在这里插入图片描述
所以NonRegisteringDriver的静态成员变量:connectionPhantomRefs, 有几万个对象, 说明了在这段时间积累了大量的数据库连接connection实例进入以下生命周期:
创建 --> 闲置 —> 回收;

我们再回头看看我们的数据库连接池的配置是怎么样的

 maxPoolSize: 80   同事写的,根本就没有这个属性-_-   所以最大默认连接数是10 maximumPoolSize控制最大连接数,默认为10
 minIdle:   2 这个属性也写错了-_-   minimumIdle控制最小连接数,默认等同于maximumPoolSize,10。
 idleTimeout: 600000  连接空闲时间超过idleTimeout 10min后连接被抛弃 此设置仅适用于minimumIdle 定义为小于maximumPoolSize 的情况
 maxLifetime: 1800000 连接生存时间超过 maxLifetime 30分钟后,连接会被抛弃.

根据上述配置可知,每隔 30 min,就会重新创建一批连接实例放入内存中,而每次新建一个数据库连接,都会把该连接放入connectionPhantomRefs集合中。

因为连接资源一般存活时间比较久,经过多次Young GC,一般都能存活到老年代。如果这个数据库连接对象本身在老年代,connectionPhantomRefs中的元素就会一直堆积,直到下次 full gc。同时如果等到full gc 的时候connectionPhantomRefs集合的元素非常多,full gc也会非常耗时。

问题找到了,哪解决方案就呼之欲出了,见下面

3.解决方案

3.1 调整HikariCP配置

HikariCP默认配置(参考:https://github.com/brettwooldridge/HikariCP)
maximumPoolSize

This property controls the maximum size that the pool is allowed to reach, including both idle and in-use connections. Basically this value will determine the maximum number of actual connections to the database backend. A reasonable value for this is best determined by your execution environment. When the pool reaches this size, and no idle connections are available, calls to getConnection() will block for up to connectionTimeout milliseconds before timing out. Please read about pool sizing. Default: 10

 Default: 10

minimumIdle

This property controls the minimum number of idle connections that HikariCP tries to maintain in the pool. If the idle connections dip below this value and total connections in the pool are less than maximumPoolSize, HikariCP will make a best effort to add additional connections quickly and efficiently. However, for maximum performance and responsiveness to spike demands, we recommend not setting this value and instead allowing HikariCP to act as a fixed size connection pool. Default: same as maximumPoolSize

Default: same as maximumPoolSize

maxLifetime

This property controls the maximum lifetime of a connection in the pool. An in-use connection will never be retired, only when it is closed will it then be removed. On a connection-by-connection basis, minor negative attenuation is applied to avoid mass-extinction in the pool. We strongly recommend setting this value, and it should be several seconds shorter than any database or infrastructure imposed connection time limit. A value of 0 indicates no maximum lifetime (infinite lifetime), subject of course to the idleTimeout setting. The minimum allowed value is 30000ms (30 seconds). Default: 1800000 (30 minutes)

Default: 1800000 (30 minutes)

idleTimeout

This property controls the maximum amount of time that a connection is allowed to sit idle in the pool. This setting only applies when minimumIdle is defined to be less than maximumPoolSize. Idle connections will not be retired once the pool reaches minimumIdle connections. Whether a connection is retired as idle or not is subject to a maximum variation of +30 seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout. A value of 0 means that idle connections are never removed from the pool. The minimum allowed value is 10000ms (10 seconds). Default: 600000 (10 minutes)

Default: 600000 (10 minutes)

connectionTimeout

This property controls the maximum number of milliseconds that a client (that's you) will wait for a connection from the pool. If this time is exceeded without a connection becoming available, a SQLException will be thrown. Lowest acceptable connection timeout is 250 ms. Default: 30000 (30 seconds)

Default: 30000 (30 seconds)

Hikari 推荐 maxLifetime 设置为比数据库的 wait_timeout 时间少 30s 到 1min。如果你使用的是 mysql 数据库,可以使用 show global variables like ‘%timeout%’; 查看 wait_timeout,默认为 8 小时
注意有些公司使用的代理连接,具体wait_timeout可以咨询自己公司的运维组。

3.2 调整mysql-connector-java

升级MySQL jdbc driver到8.0.30,开启disableAbandonedConnectionCleanup
Oracle应该是接收了大量开发人员的反馈,在高版本中已经可以通过配置选择性关闭此功能。 mysql-connector-java 版本(8.0.22+)的代码对数据库连接的虚引用有新的处理方式,其增加了开关,可以手动关闭此功能。

其版本8.0.22介绍增加此参数即是为了解决JVM 虚引用相关问题,但是默认是未启用,需要手动开启:

https://dev.mysql.com/doc/relnotes/connector-j/8.0/en/news-8-0-22.html

When using Connector/J, the AbandonedConnectionCleanupThread thread can now be disabled completely by setting the new system property com.mysql.cj.disableAbandonedConnectionCleanup to true when configuring the JVM. The feature is for well-behaving applications that always close all connections they create. Thanks to Andrey Turbanov for contributing to the new feature. (Bug #30304764, Bug #96870)

​ 有了这个配置,就可以在启动参数上设置属性:

java -jar app.jar -Dcom.mysql.cj.disableAbandonedConnectionCleanup=true

或者在代码里设置属性:

System.setProperty(PropertyDefinitions.SYSP_disableAbandonedConnectionCleanup,"true");

com.mysql.cj.disableAbandonedConnectionCleanup=true 时,生成数据库连接时就不会生成虚引用.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

赛奥分离技术现已加入2024第13届生物发酵展

参展企业介绍 上海赛奥分离技术工程有限公司成立于2010年,是上海市高新技术企业、上海市专精特新企业&#xff0c;宝山区工程及技术中心。公司着重于在流体分离净化领域&#xff0c;研究开发制造膜技术领域中错流微滤、超滤系统、纳滤系统、反渗透、精细过滤等膜分离设备及适用…

光伏无人机:绿色能源与航空技术的融合创新

在可再生能源和无人机技术快速发展的背景下&#xff0c;光伏无人机作为一种新兴的绿色航空器&#xff0c;正逐渐展现出其独特的优势和广阔的应用前景。本文将深入探讨光伏无人机的原理、优势以及其在多个领域的应用&#xff0c;展望其未来的发展趋势。 一、光伏无人机的原理 光…

【LeetCode】热题100 刷题笔记

文章目录 T1 两数之和T49 字母异位词分组常用小技巧 T1 两数之和 链接&#xff1a;1. 两数之和 题目&#xff1a; 【刷题感悟】这道题用两层for循环也能做出来&#xff0c;但我们还是要挑战一下时间复杂度小于 O ( n 2 ) O(n^2) O(n2)的解法&#xff0c;不能因为它是第一道 …

SAP Fiori开发中的JavaScript基础知识6 - 数组(Arrays)

1 背景 在本篇博客中&#xff0c;我将介绍JavaScript中数组&#xff08;Arrays&#xff09;的概念和用法。 2 数组 在JavaScript中&#xff0c;数组是一种特殊的对象&#xff0c;用于存储多个值在单个变量中。 2.1 创建数组 在JavaScript中&#xff0c;创建数组有以下有2种…

鸿蒙OS元服务开发说明:【WebGL网页图形库开发接口】

一、场景介绍 WebGL主要帮助开发者在前端开发中完成图形图像的相关处理&#xff0c;比如绘制彩色图形等。目前该功能仅支持使用兼容JS的类Web开发范式开发。 二、接口说明 表1 WebGL主要接口列表 鸿蒙OS开发更多内容↓点击HarmonyOS与OpenHarmony技术鸿蒙技术文档开发知识更…

反弹shell的方法和场景

Netcat反弹Shell 1 NC正向反弹shell Netcat简称NC,是一个简单、可靠的网络工具,被誉为网络界的瑞士军刀。通NC可以进行端口扫描、 反弹Shell、端口监听和文件传输等操作,常用参数如下&#xff1a; -c指定连接后要执行的shell命令-e指定连接后要执行的文件名-k配置 Socket一…

制造业工厂怎么通过MES系统来升级改造车间管理

在当今高度竞争的市场环境下&#xff0c;制造业企业需要不断提高生产效率&#xff0c;以在激烈的竞争中立于不败之地。而一种被广泛应用的方法就是利用MES控制系统&#xff0c;通过数字化管理和自动化控制来改造生产车间提升生产效率。 1、MES管理系统能够实现对生产过程的全面…

Linux环境基础和工具的使用

目录 1、Linux软件包管理器---yum 2、Linux开发工具 2.1、vim基本概念 2.2 vim基本操作 2.3 vim正常模式命令集 2.4 vim末行模式命令集 2.5 简单vim配置 2.5.1 配置文件的位置 3 Linux编译器--gcc/g的使用 3.1 背景知识 3.2 gcc完成 4 Linux调试器--gdb使用 4.1 背…

网页的血液——javascript

JavaScript 基础知识概述 1. JavaScript 介绍 JavaScript 是一种高级的、解释型的编程语言&#xff0c;它是一种基于对象的、事件驱动的语言&#xff0c;它允许开发者创建动态的网页。JavaScript 是一种脚本语言&#xff0c;它可以嵌入到 HTML 中&#xff0c;或者作为外部文件…

一篇商业稿件值千元,我的过稿经验大公开

撰写过上百篇企业和品牌稿件&#xff0c;甚至一篇商业稿件可值千元&#xff0c;可能很多人还不太相信&#xff0c;事实就是真的会有很多的企业和品牌愿意为此买单&#xff0c;是因为稿件带来的价值也是无法衡量的&#xff0c;直接给产品或是品牌带来更多的曝光甚至转化。今天伯…

一种新的基于STT-MRAM的时域内存计算单元

大数据、物联网和人工智能等技术的进步揭示了传统冯诺依曼的瓶颈架构&#xff0c;导致高能耗和有限的内存带宽。内存计算&#xff08;IMC通过直接在内存中进行计算&#xff0c;提高了能效&#xff0c;提供了一个有前景的解决方案计算。现有的基于时域&#xff08;TD&#xff09…

vue+elementUI搭建动态表头的表格

前提&#xff1a;以下代码是vue2项目结合elementUi完成的 数据结构 后端传来的数据是两个list&#xff0c;一个表头的list&#xff0c;一个表格内容的list // 表头 headTableAtts: [{ columnLabel: 姓名, columnName: name },{ columnLabel: 年龄, columnName: age },{ colu…

算法学习——LeetCode力扣动态规划篇4(377. 组合总和 Ⅳ、322. 零钱兑换、279. 完全平方数、139. 单词拆分)

算法学习——LeetCode力扣动态规划篇4 377. 组合总和 Ⅳ 377. 组合总和 Ⅳ - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个由 不同 整数组成的数组 nums &#xff0c;和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。 题目数据保…

苹果手机系统文件浏览技巧:实现高效的文件查找与管理

​ 目录 引言 用户登录工具和连接设备 查看设备信息&#xff0c;电池信息 查看硬盘信息 硬件信息 查看 基带信息 销售信息 电脑可对手机应用程序批量操作 运行APP和查看APP日志 IPA包安装测试 注意事项 引言 苹果手机与安卓手机不同&#xff0c;无法直接访问系统文件…

linux shell命令(进程管理、用户管理)

一、进程的概念 主要有两点&#xff1a; 1.进程是一个实体。每一个进程都有它自己的地址空间&#xff0c;一般情况下&#xff0c;包括文本区域&#xff08;text region&#xff09;、数据区域&#xff08;data region&#xff09;和堆栈&#xff08;stack region&#xff09;…

浅显易懂的简单说一下jvm内存模型

说起JVM大家都知道&#xff0c;它是运行java代码的基础。那么关于JVM 内存模型是不是很模糊 我用通俗易懂的方式说一下 我们这里先介绍 JVM 堆内存 它有两大块 包括 新生代内存&#xff0c;和老年代内存 。 为啥分为这两块&#xff0c; 你可以这样理解&#xff0c;&#xff…

【图论】【拓扑排序】1857. 有向图中最大颜色值

本文涉及的知识点 图论 拓扑排序 LeetCode1857. 有向图中最大颜色值 给你一个 有向图 &#xff0c;它含有 n 个节点和 m 条边。节点编号从 0 到 n - 1 。 给你一个字符串 colors &#xff0c;其中 colors[i] 是小写英文字母&#xff0c;表示图中第 i 个节点的 颜色 &#xf…

代码随想录算法训练营DAY14|C++二叉树Part.1|二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法

文章目录 二叉树的递归遍历思路CPP代码 二叉树的迭代遍历思路前序遍历后序遍历后序遍历 二叉树的统一迭代法 二叉树的递归遍历 144.二叉树的前序遍历、145.二叉树的后序遍历、94.二叉树的中序遍历 文章讲解&#xff1a;二叉树的递归遍历 视频讲解&#xff1a;每次写递归都要靠直…

寒冬已逝,“量子春天”正来

最近&#xff0c;全球对量子技术领域的私人投资有所下降&#xff0c;引发了一些观点认为这个领域可能正逐渐衰退。 政治家、资助者和投资者并不总是以科学为关注焦点。然而&#xff0c;某些科技领域偶尔会成为热点&#xff0c;正如20世纪50年代核能技术的兴起&#xff0c;那时人…