JVM3_数据库连接池虚引用ConnectionFinalizerPhantomReference引起的FullGC问题排查

本文主要是介绍JVM3_数据库连接池虚引用ConnectionFinalizerPhantomReference引起的FullGC问题排查,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

XOP服务运行期间,查看Grafana面板,发现堆内存周期性堆积,Full GC时间略长,需要调查下原因

image.png

目录

  • 垃圾收集器概述
    • 常见的垃圾收集器
    • 分区收集策略
    • 为什么CMS没成为默认收集器
  • 查看JVM运行时环境
  • 分析快照
    • PhantomReference虚引用

1、垃圾收集器概述

常见的垃圾收集器

按照收集策略划分

  • 新生代收集器:Serial、ParNew、Parallel Scavenge;
  • 老年代收集器:Serial Old、Parallel Old、CMS;
  • 整堆分区收集器:G1、ZGC、Shenandoah

吞吐量优先、停顿时间优先

  • 吞吐量优先:Parallel Scavenge收集器、Parallel Old 收集器。
  • 停顿时间优先:CMS(Concurrent Mark-Sweep)收集器。

吞吐量与停顿时间适用场景

  • 吞吐量优先:交互少,计算多,适合在后台运算的场景。
  • 停顿时间优先:交互多,对响应速度要求高

串行、并行、并发

  • 串行:Serial、Serial Old,垃圾回收必须暂停全部工作线程,无法利用多核优势。
  • 并行:ParNew、Parallel Scavenge、Parallel Old,并行描述的是多条垃圾收集器线程之前的关系,说明同一时间有多条垃圾收集器线程在工作,此时用户线程默认是处于等待状态。
  • 并发:CMS、G1,并发描述的是垃圾收集器线程和用户线程之间的关系

算法,参考往期博客

  • 复制算法:Serial、ParNew、Parallel Scavenge、G1
  • 标记-清除:CMS
  • 标记-整理:Serial Old、Parallel Old、G1

通过参数选择需要使用的垃圾收集器

  • -XX:+UseSerialGC,虚拟机运行在Client模式下的默认值,Serial+Serial Old。
  • -XX:+UseParNewGC,ParNew+Serial Old,在JDK1.8被废弃,在JDK1.7还可以使用。
  • -XX:+UseConcMarkSweepGC,ParNew+CMS+Serial Old。
  • -XX:+UseParallelGC,虚拟机运行在Server模式下的默认值,Parallel Scavenge+Serial Old(PS Mark Sweep)。
  • -XX:+UseParallelOldGC,Parallel Scavenge+Parallel Old。
  • -XX:+UseG1GC,G1+G1。

分区收集策略

JDK7/8使用采用分代收集比较多的垃圾收集器组合

image.png另外随着JDK版本更新,JDK9之后默认的垃圾收集器为G1,之前分代收集思想逐渐被分区收集思想代替,考虑的是收集堆内存的哪个部分才能获得收益最大,如G1、ZGC-JDK15开始准备好生产了、Shenandoah,并且随着JDK的版本的升级吞吐量、响应速度都在不断优化提升。

  • G1:开创了垃圾收集器面向局部收集的设计思路 和 基于Region的内存布局形式。不再像之前那样划代,而是把连续的堆内存划分为一块块的Region,每一个Region都可以根据需要充当之前分代区域的Eden、Survivor、老年代空间,除此之外还有一类特殊的Humongous区域专门用于存储大对象。它可以面向堆内存任何部分来组成回收集(Collection Set),衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,优先处理回收收益最大的那些 Region,这也就是 Garbage First 名字的由来。
  • Shennandoah:一款RedHat独立开发后来贡献给OpenJDK的收集器,在OracleJDK不存在,目标之一是暂停时间与堆大小无关,并且经过优化,中断时间不超过几毫秒。
  • ZGC:目标和Shennandoah类似,希望在对吞吐量影响不大的情况下(相比G1应用程序吞吐量减少不超过15%),实现任意堆内存大小都可以吧垃圾收集器停顿时间限制在十毫秒内。

深入学习参考

  • GC - Java 垃圾回收器之G1详解
  • Java Hotspot G1 GC的一些关键技术-美团技术团队
  • 新一代垃圾回收器ZGC的探索与实践-美团技术团队

为什么CMS从来没成为默认收集器

CMS(Concurrent Mark Sweep)是一种 以获取最短回收停顿时间为目标 的收集器。
在 JDK 5 发布时,HotSpot 虚拟机推出了一款在强交互应用中具有划时代意义的垃圾收集器——CMS 收集器。这款收集器是 HotSpot 中第一款真正意义上支持并发的垃圾收集器,它首次实现了让垃圾收集线程与用户线程(基本上)同时工作。
CMS 比 G1 早不了多少。CMS 从 JDK 5 开始加入,6 成熟;而 G1 是 7 加入,8 成熟,9 正式成为默认 GC 策略。此时 CMS 就被标记为 Deprecated,随后在 JDK 14 中被移除。

CMS并不是一个非常成功的GC策略,GC优化一般考虑点是吞吐量和响应时间,而CMS

  • 采用标记-清除算法,当处理器核比较少的时候,会造成比较大的负载,而且容易产生内存碎片,碎片太多无能为力的时候触发Concurrent Mode Failure还需要Serial Old来擦屁股。
  • 仅针对老年代,还需要一个新生代收集器,但是和Parallel Scavenage又不兼容,只能选择性能不如Parallel Scavenage的PerNew。
  • 需要调整的参数比较多,比G1多一倍

以上的种种,造成的结果就是 ParNew + CMS + Serial Old 的组合工作起来其实并不稳定。为了得到 CMS 那一点好处,需要付出很多的代价(包括 JVM 调参)。
CMS 相比前辈们,没有带来革命性的改变;而它的后辈们比它强太多。它自身的实现又很复杂,兼容性又差,调参也很麻烦,所以无法成为默认 GC 方案了。

参考

  • Java——七种垃圾收集器+JDK11最新ZGC
  • 面试:JVM 垃圾回收器-腾讯云开发者社区-腾讯云
  • Java 8 vs Java 17 垃圾收集器

2、查看JVM运行时环境

数据库环境是MySQL,连接池使用的是HikariPool,驱动是mysql-connector-java-8.0.21.jar,当前生产环境JVM运行参数

root     14968     1  0 223 ?       00:41:51 java -server -XX:MetaspaceSize=160m 
-XX:MaxMetaspaceSize=160m 
-Xms1024m 
-Xmx1024m 
-Xss256k 
-Duser.timezone=GMT+08 
-XX:+UseParNewGC 
-XX:+UseConcMarkSweepGC 
-XX:+CMSParallelRemarkEnabled 
-XX:CMSInitiatingOccupancyFraction=80 
-XX:+UseCMSInitiatingOccupancyOnly 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/data/serviceroot/xkw-xopqbm-api-service/xkw-xopqbm-api-service.hprof 
-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-Xloggc:/data/serviceroot/xkw-xopqbm-api-service/logs/xkw-xopqbm-api-service-gc.log 
-XX:+UseGCLogFileRotation 
-XX:NumberOfGCLogFiles=10 
-XX:GCLogFileSize=1m -jar /data/serviceroot/xkw-xopqbm-api-service/xkw-xopqbm-api-service.jar 
--spring.profiles.active=test 
--server.port=9501 
-Dons.client.logLevel=ERROR

当前服务器的java版本为1.8

[root@localmdmtest ~]# java -version
java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)

查看使用的垃圾收集器 jmap -heap pid,可以看到当前使用的垃圾收集器 ParNew+CMS+Serial Old

[root@localmdmtest ~]# jmap -heap 14968Attaching to process ID 14968, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.281-b09using parallel threads in the new generation.
using thread-local object allocation.# Concurrent Mark-Sweep GC :CMS回收器
# Mark Sweep Compact GC:    串行GC(Serial GC)
# Parallel GC with 2 thread(s): 并行GC(ParNew)# 这里看出是CMS
Concurrent Mark-Sweep GCHeap Configuration:MinHeapFreeRatio         = 40MaxHeapFreeRatio         = 70MaxHeapSize              = 1073741824 (1024.0MB)NewSize                  = 357892096 (341.3125MB)MaxNewSize               = 357892096 (341.3125MB)OldSize                  = 715849728 (682.6875MB)NewRatio                 = 2SurvivorRatio            = 8MetaspaceSize            = 167772160 (160.0MB)CompressedClassSpaceSize = 159383552 (152.0MB)MaxMetaspaceSize         = 167772160 (160.0MB)G1HeapRegionSize         = 0 (0.0MB)Heap Usage:
New Generation (Eden + 1 Survivor Space):capacity = 322109440 (307.1875MB)used     = 245758352 (234.37342834472656MB)free     = 76351088 (72.81407165527344MB)76.2965382200534% used
Eden Space:capacity = 286326784 (273.0625MB)used     = 239080888 (228.00530242919922MB)free     = 47245896 (45.05719757080078MB)83.49930965592098% used
From Space:capacity = 35782656 (34.125MB)used     = 6677464 (6.368125915527344MB)free     = 29105192 (27.756874084472656MB)18.66117484403617% used
To Space:capacity = 35782656 (34.125MB)used     = 0 (0.0MB)free     = 35782656 (34.125MB)0.0% used
concurrent mark-sweep generation:capacity = 715849728 (682.6875MB)used     = 84539240 (80.6229019165039MB)free     = 631310488 (602.0645980834961MB)11.809634996466745% used45864 interned Strings occupying 4725904 bytes.

3、分析快照

dump快照命令:jmap -dump:live,format=b,file=/home/scl/xopqbm/heapdump.hprof xxx,可以使用MAT或者在线工具https://heaphero.io/分析快照

发现11,319 instances of "com.mysql.cj.jdbc.AbandonedConnectionCleanupThread$ConnectionFinalizerPhantomReference"

image.png

也就是ConnectionFinalizerPhantomReference占了80%的堆内存,为什么会这么多对象,需要分析下原因。

PhantomReference虚引用

ConnectionFinalizerPhantomReference这个类在AbandonedConnectionCleanupThread类内定义,继承PhantomReference

// AbandonedConnectionCleanupThread类内
private static class ConnectionFinalizerPhantomReference extends PhantomReference<MysqlConnection> {private NetworkResources networkResources;ConnectionFinalizerPhantomReference(MysqlConnection conn, NetworkResources networkResources, ReferenceQueue<? super MysqlConnection> refQueue) {super(conn, refQueue);this.networkResources = networkResources;}void finalizeResources() {if (this.networkResources != null) {try {this.networkResources.forceClose();} finally {this.networkResources = null;}}}}

对于PhantomReference虚引用的概念,简单就是他可以将某个对象标记为虚的,一般用于标记对象是否被GC回收,虚引用也称为“幽灵引用”,它是最弱的一种引用关系。

image.png

  • 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
  • 为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。
  • 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,将这个虚引用加入引用队列,在其关联的虚引用出队前,不会彻底销毁该对象。所以可以通过检查引用队列中是否有相应的虚引用来判断对象是否已经被回收了。

查看AbandonedConnectionCleanupThread类内部的属性和主要的run方法,主要的属性有

  • Set connectionFinalizerPhantomRefs
  • ReferenceQueue referenceQueue
private static final Set<ConnectionFinalizerPhantomReference> connectionFinalizerPhantomRefs = ConcurrentHashMap.newKeySet();private static final ReferenceQueue<MysqlConnection> referenceQueue = new ReferenceQueue<>();private static final ExecutorService cleanupThreadExcecutorService;private static Thread threadRef = null;private static Lock threadRefLock = new ReentrantLock();

image.png

public void run() {for (;;) {try {checkThreadContextClassLoader();Reference<? extends MysqlConnection> reference = referenceQueue.remove(5000);if (reference != null) {finalizeResource((ConnectionFinalizerPhantomReference) reference);}} catch (InterruptedException e) {threadRefLock.lock();try {threadRef = null;// Finalize remaining references.Reference<? extends MysqlConnection> reference;while ((reference = referenceQueue.poll()) != null) {finalizeResource((ConnectionFinalizerPhantomReference) reference);}connectionFinalizerPhantomRefs.clear();} finally {threadRefLock.unlock();}return;} catch (Exception ex) {// Nowhere to really log this.}}
}

// TODO

参考

  • JVM 优化踩坑记
  • 数据库连接池引起的FullGC问题,看我如何一步步排查、分析、解决
  • PhantomReference 引发的GC问题-CSDN博客
  • AbandonedConnectionCleanupThread$ConnectionFinalizerPhantomReference内存溢出_abandoned connection cleanup thread-CSDN博客
  • G1垃圾回收参数调优及MySQL虚引用造成GC时间过长分析 | 京东云技术团队 - 墨天轮

这篇关于JVM3_数据库连接池虚引用ConnectionFinalizerPhantomReference引起的FullGC问题排查的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/784825

相关文章

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

oracle数据库索引失效的问题及解决

《oracle数据库索引失效的问题及解决》本文总结了在Oracle数据库中索引失效的一些常见场景,包括使用isnull、isnotnull、!=、、、函数处理、like前置%查询以及范围索引和等值索引... 目录oracle数据库索引失效问题场景环境索引失效情况及验证结论一结论二结论三结论四结论五总结ora

element-ui下拉输入框+resetFields无法回显的问题解决

《element-ui下拉输入框+resetFields无法回显的问题解决》本文主要介绍了在使用ElementUI的下拉输入框时,点击重置按钮后输入框无法回显数据的问题,具有一定的参考价值,感兴趣的... 目录描述原因问题重现解决方案方法一方法二总结描述第一次进入页面,不做任何操作,点击重置按钮,再进行下

解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题

《解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题》本文主要讲述了在使用MyBatis和MyBatis-Plus时遇到的绑定异常... 目录myBATis-plus-boot-starpythonter与mybatis-spring-b

C#实现文件读写到SQLite数据库

《C#实现文件读写到SQLite数据库》这篇文章主要为大家详细介绍了使用C#将文件读写到SQLite数据库的几种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录1. 使用 BLOB 存储文件2. 存储文件路径3. 分块存储文件《文件读写到SQLite数据库China编程的方法》博客中,介绍了文

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

SQL Server数据库磁盘满了的解决办法

《SQLServer数据库磁盘满了的解决办法》系统再正常运行,我还在操作中,突然发现接口报错,后续所有接口都报错了,一查日志发现说是数据库磁盘满了,所以本文记录了SQLServer数据库磁盘满了的解... 目录问题解决方法删除数据库日志设置数据库日志大小问题今http://www.chinasem.cn天发

mysql主从及遇到的问题解决

《mysql主从及遇到的问题解决》本文详细介绍了如何使用Docker配置MySQL主从复制,首先创建了两个文件夹并分别配置了`my.cnf`文件,通过执行脚本启动容器并配置好主从关系,文中还提到了一些... 目录mysql主从及遇到问题解决遇到的问题说明总结mysql主从及遇到问题解决1.基于mysql

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11