DBCP2/Tomcat环境下JPA查询触发IllegalMonitorStateException异常
生产环境JPA查询触发IllegalMonitorStateException异常
异常栈信息
03-Dec-2022 12:29:22.546 SEVERE [SMTP Server Worker (rm-2)] de.e_nexus.server.mail.DefaultSMTPServer$SMTPConnectionWorker$TransportSocket.<init> Created transport socket: a48fc8d5-8cf0-4c9f-88d2-201a47621987 using Socket[addr=/194.50.69.4,port=35600,localport=25] Exception in thread "Thread-27" java.lang.IllegalMonitorStateException at java.base/java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:175) at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1007) at java.base/java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:494) at org.apache.tomcat.dbcp.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:598) at org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:436) at org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:353) at org.apache.tomcat.dbcp.dbcp2.PoolingDataSource.getConnection(PoolingDataSource.java:134) at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.getConnection(BasicDataSource.java:753) at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) at org.hibernate.internal.NonContextualJdbcConnectionAccess.obtainConnection(NonContextualJdbcConnectionAccess.java:38) at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:108) at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:138) at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.connection(StatementPreparerImpl.java:50) at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:149) at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:176) at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:151) at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:2103) at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2040) at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2018) at org.hibernate.loader.Loader.doQuery(Loader.java:948) at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:349) at org.hibernate.loader.Loader.doList(Loader.java:2849) at org.hibernate.loader.Loader.doList(Loader.java:2831) at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2663) at org.hibernate.loader.Loader.list(Loader.java:2658) at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:506) at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:400) at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:219) at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1414) at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1625) at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1593) at org.hibernate.query.Query.getResultList(Query.java:165) at jdk.internal.reflect.GeneratedMethodAccessor4243.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:402) at jdk.proxy11/jdk.proxy11.$Proxy799.getResultList(Unknown Source) at de.e_nexus.web.rm.mail.veto.dnsbl.DNSBlackList.veto(DNSBlackList.java:39) at de.e_nexus.web.rm.mail.veto.dnsbl.DNSBlackList.veto(DNSBlackList.java:29) at de.e_nexus.utils.VetoCouncil$1.ask(VetoCouncil.java:87) at de.e_nexus.utils.VetoCouncil$1Survey.run(VetoCouncil.java:73)
相关代码实现
/* ___ _ * | _ \___| |___ __ _ ___ ___ Copyright 2016 * | / -_) / -_) _` (_-</ -_) https://www.e-nexus.de./ * |_|_\___|_\___\__,_/__/\___| by Peter Rader _ * | \/ |__ _ _ _ __ _ __ _ ___ _ __ ___ _ _| |_ * | |\/| / _` | ' \/ _` / _` / -_) ' \/ -_) ' \ _| * |_| |_\__,_|_||_\__,_\__, \___|_|_|_\___|_||_\__| * All rights reserved |___/ */ package de.e_nexus.web.rm.mail.veto.dnsbl; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Named; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.xbill.DNS.ARecord; import org.xbill.DNS.Lookup; import org.xbill.DNS.Record; import org.xbill.DNS.Type; import de.e_nexus.server.mail.AllowConnection; import de.e_nexus.server.mail.VetoConnection; import de.e_nexus.web.rm.entity.DNSBlacklistService; @Named public class DNSBlackList implements VetoConnection { @PersistenceContext private final EntityManager entityManager = null; public static final Logger LOG = Logger.getLogger(DNSBlackList.class.getCanonicalName()); @Override public boolean veto(final AllowConnection plan) { List<DNSBlacklistService> blacklistServices = entityManager.createQuery("FROM " + DNSBlacklistService.class.getCanonicalName(), DNSBlacklistService.class) .getResultList(); // <-- this is line 39 String hostName = plan.getIncommingIP().getHostName(); LOG.finer("Ask for host: " + hostName); for (DNSBlacklistService dnsBlacklistService : blacklistServices) { try { String url = hostName + "." + dnsBlacklistService.getDomainName(); LOG.fine("Ask url: " + url); Lookup lookup = new Lookup(url, Type.ANY); Record[] run = lookup.run(); if (run != null) { for (Record r : run) { if (r instanceof ARecord) { plan.setMessage("Your a spammer said " + dnsBlacklistService.getDomainName() + "!"); LOG.warning("Service " + dnsBlacklistService.getDomainName() + " said " + plan.getIncommingIP() + " is spammer!"); return true; } } } } catch (Exception e) { LOG.log(Level.INFO, "Cant ask DNSBL", e); } } return false; } }
环境配置
- JDK:
/root/amazon-corretto-17.0.4.9.1-linux-x64/bin/java - Tomcat:
/usr/local/share/apache-tomcat-8.5.50/ - Hibernate:
hibernate-core-5.4.28.Final.jar、hibernate-envers-5.4.28.Final.jar - 数据库连接池配置:
<Resource name="jdbc/database" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="xxx" defaultAutoCommit="false" username="xxx" password="xxx" maxActive="400" maxTotal="500" maxIdle="5" maxWait="1"></Resource>
成因分析
- 连接池并发BUG:Tomcat 8.5.50配套的DBCP2版本存在锁管理的并发缺陷,高负载下线程尝试释放未持有的ReentrantLock,触发
IllegalMonitorStateException。 - 频繁数据库查询加剧压力:每次
veto请求都直接查询数据库获取黑名单列表,无缓存机制,高并发下频繁调用连接池,大幅提升触发BUG的概率。 - EntityManager使用方式:容器管理的EntityManager在非事务环境下,每次查询都会从连接池获取新连接,进一步增加连接池的调用频率和并发争抢。
解决方案
1. 修复连接池BUG
升级Tomcat到8.5.70及以上版本(该版本修复了DBCP2的锁相关问题),或单独将DBCP2依赖升级至2.9.0+版本。
2. 添加缓存减少数据库查询
黑名单列表属于低频变更数据,添加缓存避免重复查询:
// 示例:使用Spring Cache缓存查询结果 @Cacheable(value = "dnsBlacklist") public List<DNSBlacklistService> getCachedBlacklistServices() { return entityManager.createQuery("FROM " + DNSBlacklistService.class.getCanonicalName(), DNSBlacklistService.class) .getResultList(); } // 修改veto方法调用缓存方法 @Override public boolean veto(final AllowConnection plan) { List<DNSBlacklistService> blacklistServices = getCachedBlacklistServices(); // 后续逻辑不变 }
3. 优化连接池配置
调整配置参数,降低并发争抢和连接频繁销毁重建的开销:
<Resource name="jdbc/database" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="xxx" defaultAutoCommit="false" username="xxx" password="xxx" maxTotal="500" maxIdle="300" maxWait="5000"></Resource>
- 移除
maxActive:DBCP2中maxTotal已代表总连接数,maxActive是旧版兼容配置,重复配置易引发混淆。 - 调大
maxIdle:设置为接近maxTotal的值,减少连接频繁创建销毁的开销。 - 调大
maxWait:从1毫秒改为5000毫秒,避免短时间内连接耗尽导致的线程争抢锁。
4. 规范EntityManager使用
确保在事务环境下使用EntityManager(如果需要),结合缓存减少连接池调用次数,降低并发压力。
内容的提问来源于stack exchange,提问作者Grim
相关产品推荐
相关产品推荐

