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

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 时,生成数据库连接时就不会生成虚引用.

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

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

相关文章

FastEI论文阅读

前言 研究FastEI有很长时间了&#xff0c;现在来总结一下&#xff0c;梳理一下认知。论文地址&#xff1a;https://www.nature.com/articles/s41467-023-39279-7&#xff0c;Github项目地址&#xff1a;https://github.com/Qiong-Yang/FastEI。 概要 这篇文章做的工作是小分子…

【御控物联】JavaScript JSON结构转换(10):数组To数组——转换映射方式

文章目录 一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON数组 To JSON数组》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0…

鸿蒙开发-ArkTS语言-基础类库

鸿蒙开发-UI-交互事件-通用事件 鸿蒙开发-UI-交互事件-键鼠事件 鸿蒙开发-UI-交互事件-焦点事件 鸿蒙开发-UI-交互事件-手势事件 鸿蒙开发-UI-web 鸿蒙开发-UI-web-页面 文章目录 前言 一、ArkTS语言基础类库概述 1. 异步并发和多线程并发能力 2. 提供常见容器类库的增删改查能…

电机选型与直线模组(简化版)

2-1电机分类和普通电机选型 1、电机及其原理的基本认知&#xff1a; 电动机我们平时简称电机&#xff0c;大部分情况下指的都是通电后&#xff0c;输出轴会旋转运动的设备。即将电能转换为机械能&#xff0c;供我们使用以驱动机构运行。电机简单的讲&#xff0c;其实就是一个…

用python,将有道词典中的生词导入扇贝单词

我试过有道词典和扇贝单词&#xff0c;个人感觉扇贝单词记忆功能非常好用&#xff0c;但是扇贝单词没有pc版&#xff0c;而有道在这方面就做的很好。博主平时都是用有道查生词&#xff0c;那有没有办法将有道词典中的生词导入扇贝中呢&#xff1f;下面的过程看上去很复杂&#…

护眼台灯什么牌子好一点,五大热销护眼台灯品牌推荐

台灯已成为每个家庭中不可或缺的照明设备&#xff0c;它的作用不仅限于在夜晚提供充分的光亮&#xff0c;还能迅速营造出适宜的氛围&#xff0c;为用眼提供一个更佳的环境。随着生活品质的提高&#xff0c;人们对台灯的期望也逐步升级&#xff0c;智能化和护眼功能逐渐成为消费…

Python文件操作命令

文件操作 我知道你最近很累&#xff0c;是那种看不见的、身体上和精神上的疲惫感&#xff0c;但是请你一定要坚持下去。就算无人问津也好&#xff0c;技不如人也好&#xff0c;千万别让烦躁和焦虑毁了你的热情和定力。别贪心&#xff0c;我们不可能什么都有&#xff0c;也别灰心…

C++STL(vector类)

文章目录 1.vector类的介绍2.vector的基本用法2.1 遍历vector2.2 数据插入vector2.3 vector容量2.4 查找元素2.5 vector包含vector(二维数组)2.6 迭代器失效2.6 迭代器覆盖 3.vector的底层(模拟实现)3.1 begin and end3.2 拷贝3.3 赋值3.4 size and capacity3.5 operator[]3.6 …

TP4054替代DP4054锂电池供电电路保护方案

锂离子电池以其优良的特性&#xff0c;被广泛应用于&#xff1a;手机、摄录像机、笔记本电脑、无绳电话、电动工具、遥控或电动玩具、照相机等便携式电子设备中。 01 电池特点 1、具有更高的重量能量比、体积能量比; 2、电压高&#xff0c;单节锂电池电压为3.6V&#xff0c;等…

手术信息处理系统源码 B/S网页版手麻系统源码

手术信息处理系统源码 B/S网页版手麻系统源码 手术信息处理系统的工作主要为术前、术中、术后三个阶段的信息管理提供支持。术前是手术预约安排信息的处理&#xff0c;系统为麻醉师和手术相关人员提供病人的病历、检查和检验结果等信息&#xff0c;以帮助他们全面了解病人情况…

2024年最受欢迎的 19 个 VS Code 主题排行榜

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

【Redis】redis集群模式

概述 Redis集群&#xff0c;即Redis Cluster&#xff0c;是Redis 3.0开始引入的分布式存储方案。实际使用中集群一般由多个节点(Node)组成&#xff0c;Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点&#xff1a;只有主节点负责读写请求和集群信息的维护&#…

专题三_二分查找(2)

目录 35. 搜索插入位置 解析 题解 852. 山脉数组的峰顶索引 解析 题解 162. 寻找峰值 解析 题解 35. 搜索插入位置 35. 搜索插入位置 - 力扣&#xff08;LeetCode&#xff09; 解析 题解 class Solution { public:int searchInsert(vector<int>& nums, int…

【R】Error in library(foreach) : 不存在叫‘foreach’这个名字的程辑包

Error in library(foreach) : 不存在叫‘foreach’这个名字的程辑包 此外: Warning message: package ‘parallel’ is a base package, and should not be updated 解决方法 缺少名为 foreach 的包&#xff0c;使用install.packages("foreach")将名为foreach 的包…

从学习海底捞到学习巴奴,中国餐饮带洋快餐重归“产品主义”

俗话说“民以食为天”&#xff0c;吃饭一向是国人的头等大事&#xff0c;餐饮业也是经济的强劲助推力。新世纪以来&#xff0c;餐饮业不断讲述着热辣滚烫的商业故事。 2006年&#xff0c;拥有“必胜客”、“肯德基”等品牌的餐饮巨头百胜集团&#xff0c;组织两百多名区域经理…

知识图谱表示

文章目录 知识表示知识表示研究符号表示方法向量表示方法 知识表示 简单而言&#xff0c;知识表示&#xff08;KR&#xff09;就是用易于计算机处理的方式来描述人脑的知识的方法。KR不是数据格式、不等同于数据结构、也不是编程语言&#xff0c;对于人工智能而言&#xff0c;数…

SAP-怎么查一个帐号访问事务代码的记录或者一个事务代码的被访问记录

起因 上周六&#xff0c;查了某用户对某事务代码的访问记录。今天又要查类似的信息。我发现我居然忘了该怎么查了。于是在处理完事情之后&#xff0c;整理了查询的过程&#xff0c;形成了这篇文章。 经过 热心网友告诉了我一个事务代码&#xff1a;ST03N - 工作负载和性能统…

【Angular】什么是Angular中的APP_BASE_HREF

1 概述: 在这篇文章中&#xff0c;我们将看到Angular 10中的APP_BASE_HREF是什么以及如何使用它。 APP_BASE_HREF为当前页面的基础href返回一个预定义的DI标记。 APP_BASE_HREF是应该被保留的URL前缀。 2 语法: provide: APP_BASE_HREF, useValue: /gfgapp3 步骤: 在app.m…

深度学习-机器视觉part2

深度学习-机器视觉part2 文章目录 深度学习-机器视觉part2一、从卷积到卷积神经网络二、手撕卷积代码2.1 动机2.2 数据集2.3 卷积操作2.3.1 填充&#xff08;padding&#xff09;2.3.2 卷积块2.3.3 池化2.3.4 Softmax 2.4 完整CNN2.5 训练改进 三、经典CNN模型介绍四、CNN模型的…

YPay源支付V7开源版

YPay_V7版本即将停止维护更新&#xff0c;同时我们将开放最新版开源代码供学习和参考。虽然首批阶段的【function_8.1.php文件是加密的】&#xff0c;但授权已经除去&#xff0c;该代码将在新版YPay上线时开放给大家。我们也会定期进行迭代更新&#xff0c;随后将创建对应仓库&…