but has failed to stop it. This is very likely to create a memory leak(c3p0在Spring管理中,连接未关闭导致的内存溢出)

本文记录了一个关于C3P0连接池在应用停止时未能正确释放资源导致的内存泄漏问题。通过分析错误日志,发现未在Spring配置文件中为dataSource指定销毁方法close,最终引发了一系列内存泄漏警告。

以下是错误日志信息:

严重: The web application [/news] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
2014-12-19 0:27:47 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
严重: The web application [/news] appears to have started a thread named [C3P0PooledConnectionPoolManager[identityToken->1pyjusp96q571f188m8|18782a7]-AdminTaskTimer] but has failed to stop it. This is very likely to create a memory leak.
2014-12-19 0:27:47 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
严重: The web application [/news] appears to have started a thread named [C3P0PooledConnectionPoolManager[identityToken->1pyjusp96q571f188m8|18782a7]-HelperThread-#0] but has failed to stop it. This is very likely to create a memory leak.
2014-12-19 0:27:47 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
严重: The web application [/news] appears to have started a thread named [C3P0PooledConnectionPoolManager[identityToken->1pyjusp96q571f188m8|18782a7]-HelperThread-#1] but has failed to stop it. This is very likely to create a memory leak.
2014-12-19 0:27:47 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
严重: The web application [/news] appears to have started a thread named [C3P0PooledConnectionPoolManager[identityToken->1pyjusp96q571f188m8|18782a7]-HelperThread-#2] but has failed to stop it. This is very likely to create a memory leak.


堆栈信息:

Exception in thread "C3P0PooledConnectionPoolManager[identityToken->1pyjusp96q571f188m8|18782a7]-AdminTaskTimer" java.lang.NullPointerException
    at com.mchange.v2.log.log4j.Log4jMLog$Log4jMLogger.isLoggable(Log4jMLog.java:293)
    at com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector.run(ThreadPoolAsynchronousRunner.java:729)
    at java.util.TimerThread.mainLoop(Timer.java:512)
    at java.util.TimerThread.run(Timer.java:462)



我的情况是在配置数据源时未配置销毁dataSource对象时执行close方法关闭连接

	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
		<property name="driverClass" value="${driverClass}"></property>
		<property name="jdbcUrl" value="${jdbcUrl}"></property>
		<property name="user" value="${user}"></property>
		<property name="password" value="${password}"></property>

		<property name="initialPoolSize" value="1"></property>
		<property name="minPoolSize" value="1"></property>
		<property name="maxPoolSize" value="20"></property>
		<property name="maxIdleTime" value="60"></property>
	</bean>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > 在这里面加入destroy-method="close"

<think> 我们面对的核心问题是:在引入bes-websocket依赖后,ServerEndpointExporter创建失败,同时出现一个名为`Informix-PreparedStatementCacheCleanupThread`的线程能停止,可能导致内存泄漏。我们需要解决两个相互关联的问题: 1. ServerEndpointExporter创建失败的原因及修复 2. 由第一个问题引发的Informix线程能停止(内存泄漏风险) 根据引用[2]中的堆栈信息,ServerEndpointExporter在afterPropertiesSet方法中失败(第106行)。结合引用[1]和引用[4],该异常通常是由于没有检测到@ServerEndpoint注解的端点注册导致的。但是,用户已经创建了WebsocketServer(监听/websocket/*),所以我们需要检查: - 该WebsocketServer类是否被正确扫描到(是否在Spring的组件扫描路径下?) - 类上是否使用了@ServerEndpoint注解?是否有@Component注解?(如引用[4]中的示例) 另外,引入bes-websocket依赖可能带来了冲突,导致Spring容器无法正常注册端点。 第二个问题:`Informix-PreparedStatementCacheCleanupThread`线程停止。这个线程通常与Informix数据库连接池有关,可能是由于WebSocket初始化失败导致整个应用上下文没有正确关闭,进而使得数据库连接池没有正常关闭。 解决思路: 步骤1:修复ServerEndpointExporter创建失败的问题 - 确保WebSocket端点类(如WebsocketServer)被正确注解和扫描 - 排除依赖冲突(如之前方案) - 检查ServerEndpointExporter的配置(使用条件注解避免重复创建,如引用[3]所示) 步骤2:确保应用正常关闭,从而关闭数据库连接池 - 在应用关闭时,确保数据库连接池被正确销毁 具体措施: ### 1. 解决ServerEndpointExporter创建失败 **a. 检查端点类** 确保端点类类似如下: ```java @Component @ServerEndpoint("/websocket") public class WebsocketServer { // 事件处理方法 } ``` **b. 配置ServerEndpointExporter** 参考引用[3]和[4],在配置类中创建ServerEndpointExporter的Bean,并且使用条件注解避免重复创建(因为bes-websocket可能已经创建了一个): ```java @Configuration public class WebSocketConfig { @ConditionalOnMissingBean // 确保只有一个实例 @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } ``` **c. 排除依赖冲突** 在引入bes-websocket时排除冲突的依赖(如之前回答): ```xml <dependency> <groupId>com.bes.appserver</groupId> <artifactId>bes-websocket</artifactId> <version>9.5.5.013</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </exclusion> <exclusion> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-websocket</artifactId> </exclusion> </exclusions> </dependency> ``` **d. 检查端点扫描** 如果端点类不在Spring Boot的主扫描包下,需要将其添加到扫描路径(使用@ComponentScan)或移动到主包下。 ### 2. 解决Informix线程停止的问题(内存泄漏) 这个线程是由Informix JDBC驱动创建的,用于清理PreparedStatement缓存。正常情况下,当应用关闭时,数据库连接关闭会停止该线程。 如果ServerEndpointExporter初始化失败导致整个Spring上下文没有正常启动或关闭,可能会影响连接池的销毁。 因此,在修复WebSocket问题后,应用正常关闭时线程应该会被停止。 如果问题仍然存在,可以手动注册一个关闭钩子来关闭数据库连接池(以DBCP2为例): ```java @PreDestroy public void destroy() { // 关闭连接池 dataSource.close(); } ``` 或者,在配置中确保连接池有销毁方法: ```yaml spring: datasource: tomcat: remove-abandoned-timeout: 10 # 设置连接回收超时 remove-abandoned: true ``` 如果使用的是HikariCP,它会在应用关闭时自动关闭,不需要额外配置。 ### 3. 验证 启动应用,检查: - WebSocket是否正常工作 - 停止应用,检查日志中是否有数据库连接关闭的日志,以及`Informix-PreparedStatementCacheCleanupThread`是否停止。 如果上述方法能解决,可能需要: - 升级Informix JDBC驱动(可能有已知的内存泄漏问题) - 检查应用关闭流程是否完整(是否有其他非守护线程阻止JVM退出) 总结:核心在于修复WebSocket初始化问题,确保应用正常启动和关闭,从而保证资源(包括数据库连接池)被正确释放。 §§相关问题§§ 1. 如何解决Spring Boot应用关闭时资源释放的问题? 2. Informix JDBC驱动有哪些常见的内存泄漏问题及解决方案? 3. 如何确保自定义线程在Spring Boot应用关闭时正确停止? </think> ### 解决 `Informix-PreparedStatementCacheCleanupThread` 线程停止导致内存泄漏的方法 --- #### 问题根源分析 当引入 `bes-websocket` 依赖且 `ServerEndpointExporter` 创建失败时,会导致: 1. **WebSocket 初始化中断**:`ServerEndpointExporter` 注册失败(如引用[2]所述)[^2] 2. **资源释放**:数据库连接池的清理线程 `Informix-PreparedStatementCacheCleanupThread` 无法正常终止 3. **内存泄漏风险**:该线程持续持有数据库连接资源,最终导致内存泄漏 --- ### 解决方案 #### 1. **修复 `ServerEndpointExporter` 创建问题(根本原因)** 参考引用[3][^3],确保正确配置: ```java @Configuration public class WebSocketConfig { @Bean @ConditionalOnMissingBean // 防止重复创建 public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } ``` #### 2. **强制关闭 Informix 线程(应急处理)** 在 Spring Boot 关闭钩子中手动终止线程: ```java @PreDestroy public void destroy() { // 查找并终止 Informix 线程 Thread.getAllStackTraces().keySet().stream() .filter(thread -> thread.getName().contains("Informix-PreparedStatementCacheCleanupThread")) .forEach(thread -> { thread.interrupt(); // 发送中断信号 try { thread.join(1000); // 等待1秒确保停止 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); // 关闭数据库连接池(关键!) DataSource dataSource = applicationContext.getBean(DataSource.class); if (dataSource instanceof AutoCloseable) { ((AutoCloseable) dataSource).close(); } } ``` #### 3. **配置数据库连接池参数** 在 `application.properties` 中增加 Informix 连接池清理设置: ```properties # 设置连接最大空闲时间(秒) spring.datasource.hikari.max-lifetime=600000 # 启用空闲连接清理 spring.datasource.hikari.idle-timeout=30000 # 直接关闭 Informix 缓存线程 ifx.ifxusercachecleanup=0 # 0表示禁用 ``` #### 4. **验证线程终止** 在应用关闭时检查日志: ```java @SpringBootApplication public class App implements DisposableBean { @Override public void destroy() { boolean isThreadAlive = Thread.getAllStackTraces().keySet().stream() .anyMatch(t -> t.getName().contains("Informix")); log.info("Informix线程状态: {}", isThreadAlive ? "运行中❌" : "已终止✅"); } } ``` --- ### 根本原因说明 | 现象 | 原因 | 解决方案 | |------|------|----------| | `ServerEndpointExporter` 初始化失败 | `bes-websocket` 依赖冲突或端点注册 | 排除冲突依赖[^1],检查 `@ServerEndpoint` 注解 | | Informix 线程停止 | 数据库连接正确关闭 | 显式关闭连接池(`@PreDestroy`) | | 内存泄漏 | 线程持有 JDBC 连接资源 | 配置 `max-lifetime` 和 `idle-timeout` | > **关键点**:通过 `@PreDestroy` + 连接关闭 + 线程主动中断,可确保资源释放(引用[4]的配置思想)[^4]。 --- ### 验证步骤 1. 启动应用后调用 `/actuator/health` 检查数据库状态 2. 停止应用时观察日志: ``` [ThreadMonitor] Informix-PreparedStatementCacheCleanupThread interrupted [ConnectionPool] HikariPool shutdown completed [AppContext] Informix线程状态: 已终止✅ ``` 3. 使用 `jconsole` 检查线程是否消失
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值