You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

成因分析

  1. 连接池并发BUG:Tomcat 8.5.50配套的DBCP2版本存在锁管理的并发缺陷,高负载下线程尝试释放未持有的ReentrantLock,触发IllegalMonitorStateException。
  2. 频繁数据库查询加剧压力:每次veto请求都直接查询数据库获取黑名单列表,无缓存机制,高并发下频繁调用连接池,大幅提升触发BUG的概率。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 08:50:24