今日排查一个递归导致的StackOverflow 问题时, 尝试用jconsole.exe连到 IDEA 中跑的java程序所在的进程ID时, 一直报连接错误, 如下:
重试几次都没什么效果, 随查看防火墙的设置情况, 发现防火墙已关闭. 排除这方面的问题.
如果是Linux, 可以考虑设置防护墙通过策略:
①. 打开防火墙文件: vi /etc/sysconfig/iptables
②. 添加-A INPUT -m state --state NEW -m tcp -p tcp --dport 8022 -j ACCEPT, 需与(Dcom.sun.management.jmxremote.rmi.port一致)
③. 重启防火墙, 使其生效 :service iptables restart
后经调试发现, 未合理的配置JXM参数.
JMX(JAVA 管理拓展) 配合Jconsole 来使用是用来分析JVM状态, 进而发现进程潜在问题的必要途径, 故合理配置JMX参数至关重要。
遂尝试使用如下jmx配置参数进行调试, 可以通过测试.
核心配置参数如下:
# 设置jmx远程配置
-Dcom.sun.management.jmxremote
# 设置jmx远程配置的port
-Dcom.sun.management.jmxremote.rmi.port=8022
# 设置jmx远程配置的ssl
-Dcom.sun.management.jmxremote.ssl=false
# 设置jmx远程配置的鉴权
-Dcom.sun.management.jmxremote.authenticate=false
# 设置jmx远程配置的host地址
-Djava.rmi.server.hostname=localhost
IDEA中的配置样例如下:
引申:
java.lang.StackOverflowError: null
异常的一般的原因是:
程序产生了死循环
或 发生无限递归
.
StackOverflow问题起因:
程序在使用MyBatis-Plus时, 在处理一段插入或更新逻辑(insertOrUpdate)时, 自己准备覆盖 MyBatis-Plus里 BaseMapper 里的 insertOrUpdate方法, 以实现自定义判断更新的数据条目, 而非根据Entity中所有字段实现全量更新覆盖. 但在方法命名时, 错误的命名为了update方法, 导致后面在处理数据库中没有的业务条目时, 原意是想使用MP里BaseMapper的insert方法, 但实际上是递归调用了该自定义方法.
这将导致所有的操作压力, 全部指向如下数据库操作语句selectList :
List existsList = selectList(condition);
/**
* 业务方法, 会调用如下insert(实际上应为: insertOrUpdate)
* 来处理Entity 在数据库中的 插入或更新问题.
*/
public void myBizImpl () {
insert(softwareDownload);
}
/**
* Biz Desc :
*
* 软件下载情况统计数据到DB,
* 如果数据库存在该联合主键的数据, 即进行更新;
* 如果不存在, 即进行插入.
*
* @param softwareDownload Model层的软件下载业务Entity
*/
public boolean insert(SoftwareDownload softwareDownload){
Wrapper condition = Condition.create().eq("DOWNLOAD_DATE", softwareDownload.getDownloadDate()).and().
eq("DOWNLOAD_CHANNEL", softwareDownload.getDownloadChannel());
List existsList = selectList(condition); // MP里baseMapper里的方法
if ( null != existsList && 0 < existsList.size() ) {
return update(softwareDownload, condition);
} else {
return insert(softwareDownload);
}
}
解决方案:
将上述方法名 insert 改为 insertOrUpdate, 目的是覆盖BaseMapper的insertOrUpdate方法, 用以实现自定义更新.
修改后的方法头为 :
public boolean insertOrUpdate(SoftwareDownload softwareDownload){
}
其实上述实现数据库insertOrupdate方法 尚存在一些性能问题, 每次操作都会进行一次query检查操作, 更为优雅的详见引用列表[^1].
通过查询数据库的压力时, 可以顺便跟踪一下数据库连接池的底层参数配置, 比如 maxActiveSize的默认配置(可以在application-druid.yml里进行覆盖).
这时, 查询mysql连接进程数, 看到如下有8个连接.
show PROCESSLIST;
结合DruidAbstractDataSource源码查看, 确实是8个:
引用列表:
[^1] MySql实现无则插入有则更新的解决方案