MySQL 连接池配置及 FullGC 分析

举报
若明天不见 发表于 2023/12/02 21:21:48 2023/12/02
【摘要】 本文主要讲述MySQL连接池配置不合适时,由于MySQL以虚引用的方式作为线程清理的后备手段,导致JVM年老代随时间缓慢增长,直至FullGC的问题。为了优化数据库连接池配置,使得JVM进行尽量少的FullGC导致服务故障,本文提供了多种解决方案 单独的MySQL清理,负责关闭被遗弃的MySQL连接,即没有被显式关闭的连接。发现容器服务的老年代在不断的增长,直到。

本文主要讲述MySQL连接池配置不合适时,由于MySQL以虚引用的方式作为线程清理的后备手段,导致JVM年老代随时间缓慢增长,直至FullGC的问题。为了优化数据库连接池配置,使得JVM进行尽量少的FullGC导致服务故障,本文提供了多种解决方案

问题描述

发现容器服务的老年代在不断的增长,直到FullGC回收

年老代使用比例

MAT dump分析

使用Java自带的jmapjstack,Java 8 引入的jcmd,或Arthas heapdump 等工具,来进行Thread dump

使用MAT进行堆栈日志分析

dump-overview

Leak Suspect

leak-suspect

mysql-connector-java-5.1.49com.mysql.jdbc.AbandonedConnectionCleanupThread单独的MySQL清理,负责关闭被遗弃的MySQL连接,即没有被显式关闭的连接

AbandonedConnectionCleanupThread

ConcurrentHashMap类型的connectionFinalizerPhantomRefs常量,包含了一系列的虚引用。目的是作为保底操作:当connection对象回收时,顺便回收相关资源

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

四种引用类型可见Java 垃圾收集器与内存分配策略

trackConnection

当创建新的连接的时候就会调用trackConnection方法,把MysqlConnection添加到虚引用的ConcurrentHashMap中

由此可以分析出造成年老代不断增长的原因是MySQL连接短时间内不断创建回收的

weak refs processing处理逻辑

此段原文自 https://www.jianshu.com/p/2db280229343

void ReferenceProcessor::process_discovered_references(
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor) {
  NOT_PRODUCT(verify_ok_to_handle_reflists());
 
  assert(!enqueuing_is_done(), "If here enqueuing should not be complete");
  // Stop treating discovered references specially.
  disable_discovery();
 
  bool trace_time = PrintGCDetails && PrintReferenceGC;
  // Soft references
  {
    TraceTime tt("SoftReference", trace_time, false, gclog_or_tty);
    process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,
                               is_alive, keep_alive, complete_gc, task_executor);
  }
 
  update_soft_ref_master_clock();
 
  // Weak references
  {
    TraceTime tt("WeakReference", trace_time, false, gclog_or_tty);
    process_discovered_reflist(_discoveredWeakRefs, NULL, true,
                               is_alive, keep_alive, complete_gc, task_executor);
  }
 
  // Final references
  {
    TraceTime tt("FinalReference", trace_time, false, gclog_or_tty);
    process_discovered_reflist(_discoveredFinalRefs, NULL, false,
                               is_alive, keep_alive, complete_gc, task_executor);
  }
 
  // Phantom references
  {
    TraceTime tt("PhantomReference", trace_time, false, gclog_or_tty);
    process_discovered_reflist(_discoveredPhantomRefs, NULL, false,
                               is_alive, keep_alive, complete_gc, task_executor);
  }
 
  // Weak global JNI references. It would make more sense (semantically) to
  // traverse these simultaneously with the regular weak references above, but
  // that is not how the JDK1.2 specification is. See #4126360. Native code can
  // thus use JNI weak references to circumvent the phantom references and
  // resurrect a "post-mortem" object.
  {
    TraceTime tt("JNI Weak Reference", trace_time, false, gclog_or_tty);
    if (task_executor != NULL) {
      task_executor->set_single_threaded_mode();
    }
    process_phaseJNI(is_alive, keep_alive, complete_gc);
  }
}

看JVM源码,weak refs processing主要包括SoftReferenceWeakReferenceFinalReferencePhantomReference以及JNI Weak Reference这五种Reference对象的处理,处理的主要内容是对之前标记的Reference对象重新处理,重新判断是否需要标记(不标记就是要回收的),如果不标记就需要放到refqueue里,等待java ReferenceHandler线程处理

优化方法

调整数据库连接池配置

如果应用程序使用Hikari或任何其他连接池管理器且并在正确的配置下,这些引用将在MinorGC时被回收,不会造成问题

  1. 适当延长idleTimeout(默认值为10分钟,问题进程设置为30s)
  2. 适当延长maxLifetime(数据库默认的连接空闲时间是8小时,问题进程设置为25min)
  3. 调整空闲线程数minimumIdle(默认会和最大连接数一样为100)

Hikari官方强烈推荐设置maxLifetime,并只比数据库引擎connectionTimeout短一些

spring:
  datasource:
	test:
	  # 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),默认:10分钟
	  idle-timeout: 1800000
	  # 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms,建议设置比数据库超时时长少60秒,参考MySQL wait_timeout参数(show variables like '%timeout%';) -->  ) -->
	  max-lifetime: 14400000
	  # 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count)
      maximum-pool-size: 100
      # 最小连接数
      minimum-idle: 10

设置JVM参数

在Java参数中设置了-Dcom.mysql.cj.disableAbandonedConnectionCleanup=true,停止生成这些引用

删除connectionFinalizerPhantomRefs集合

数据库连接的释放有连接池保证,这个保底机制其实是多余的,可以起个定时任务线程,去清理这个connectionFinalizerPhantomRefs集合。

下面是一个旧版本的清理示例代码。


参考资料:

  1. MySQL AbandonedConnectionCleanupThread Memory Leak
  2. 记一次数据库连接池导致的OOM的问题
  3. 数据库连接池配置不当导致的full gc问题记录
  4. HikariCP
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。