java -- possibly out of memory or process/resources limits reached
问题:
java运行的时候异常退出:java.lang.outofMemoryError:unabletocreatenativethread: possibly out of memory or process/resources limits reached
排查:
查看 Java 进程当前已使用的线程数
知道限制后,还需要知道当前已经用了多少线程。
方法一:通过 /proc 文件系统
bash
cat /proc/
输出类似:Threads: 120,表示该进程当前有 120 个线程。
方法二:使用 jstack 或 jvisualvm 等工具
bash
jstack
统计线程转储中线程名的行数。
方法三:使用 ps 命令统计某个进程的线程数
bash
ps -T -p
(减去标题行,实际线程数为结果减 1)
现场日志:
1.查看当前进程限制:
ps -ef | grep java|grep webservice.jar
cat /proc/{pid}/limits | grep "Max processes"
结果为65535
-
查看当前进程已使用
ps -ef | grep java|grep webservice.jar
ps -T -p {pid} | wc -l
结果为21737 -
dmesg -T|grep oom 没有数据
-
free -g
total used free shared buff/cache available
Mem: 30 13 2 0 13 15
Swap: 15 0 15 -
top 虚拟内存一直再涨
虚拟内存37.2g , RES 9.4G -
ulimit -v
unlimited -
cat /proc/sys/kernel/threads-max
246084
java 现成泄漏定位日志:
-
获取线程快照:线程快照(Thread Dump)是分析问题的核心证据。它记录了某一瞬间所有线程的执行状态。使用JDK自带的 jstack 工具就可以轻松生成:
jstack -l <Java进程PID> > thread_dump.log -
分析日志:
1)大量线程处于WAITING或TIMED_WAITING状态,但“活锁”或线程池膨胀。
转储文件开头显示 length=6793,意味着您的JVM中创建了 6793个线程。这是一个非常巨大的数字,是当前最需要关注的问题。
2)
"Druid-ConnectionPool-Create-468238626" #43 [1000077] daemon prio=5 os_prio=0 cpu=418.14ms elapsed=66905.98s tid=0x00007f053d14aaf0 nid=1000077 waiting on condition [0x00007f04be9eb000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@21.0.9/Native Method)- parking to wait for <0x00000005800008e8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@21.0.9/LockSupport.java:371)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.9/AbstractQueuedSynchronizer.java:519)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.9/ForkJoinPool.java:3780)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.9/ForkJoinPool.java:3725)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.9/AbstractQueuedSynchronizer.java:1746)
at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2566)
- parking to wait for <0x00000005800008e8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
代码分析
这是一个动态数据源管理类,用于管理多个数据库连接。
- 动态数据源路由
继承AbstractRoutingDataSource,实现动态切换数据源
determineCurrentLookupKey() 决定当前线程使用哪个数据源
- 数据源加载和初始化
java
public void loadDataSources()
从数据库(gdb_manager表)加载所有有效的数据源配置
为每个配置创建Druid连接池
缓存到dataSourceMap中
- 创建数据源连接池
buildDataSource和buildAndTestDataSource方法:
构建MySQL JDBC URL(使用黄金分片数据库)
设置连接池参数(初始连接数、最大连接数等)
配置超时和重试机制
解密密码(使用AES)
代码问题定位
这个类的设计意图是好的——动态管理多个数据源。但其实现方式正是导致数千个Druid线程被创建的根本原因。
关键问题代码:
构造函数中调用 loadDataSources()
public DynamicDataSource(GdbManagerMapper gdbManagerMapper) {
this.gdbManagerMapper = gdbManagerMapper;
this.loadDataSources(); // 初始化时加载配置
}
DynamicDataSource 本身是一个Spring Bean(由 @Component 注解标记)。当Spring容器启动时,会创建这个Bean的实例。在这个过程中,loadDataSources() 方法被调用,它会查询数据库并为每条 GdbManager 记录创建一个 DruidDataSource。
loadDataSources() 方法每次都会创建新数据源
public void loadDataSources() {
// ... 查询数据库,获取gdbManagers列表 ...
gdbManagers.forEach(config -> {
DataSource ds = null;
try {
ds = buildDataSource(config); // 每次都创建全新的DruidDataSource
dataSourceMap.put(config.getId().toString(), ds);
} catch (Exception e) {
// ...
}
});
// ...
}
这个方法可以在运行时被多次调用! 例如,可能有一个定时任务或一个管理接口,会定期调用 loadDataSources() 来刷新数据源配置。每次调用,它都会为每一个 config 创建一个全新的 DruidDataSource 实例。
dataSourceMap 的成员变量
private final Map<Object, Object> dataSourceMap = new ConcurrentHashMap<>();
这个Map用来存储所有数据源。当新的数据源被创建并 put 进这个Map时,旧的数据源(如果key相同)会被覆盖。但是,被覆盖的旧 DruidDataSource 并没有被关闭! 它们变成了无人引用的对象,但它们的内部线程(CreateConnectionThread 和 DestroyConnectionThread)却依然在运行,无法被垃圾回收。

浙公网安备 33010602011771号