Spring Boot 启动失败:先定阶段,再进分支
把启动失败拆成 6 个阶段、12 条可对号入座的分支:先读 Description 与 Action,再确认卡在哪个启动阶段,最后按报错签名展开分支,用判据式命令锁定成因。大多数启动失败不需要改代码,问题出在环境或配置。

按首个可操作的异常分流
先读完整异常链与 Failure Analysis,记录运行环境和生效配置,再按具体签名选择路径。每条路径统一为六段:当前环境、报错识别、快速解法、验证、解法无效时、相关。
当前环境
先记录 Spring Boot 版本、构建 JDK、编译工具链、部署镜像、容器运行时和实际启动命令。容器运行时必须在容器内核验 java -version,不能用构建机或宿主机版本代替。
- 确认报错类来自应用产物还是第三方 JAR,并核对框架、插件和依赖的 Java 支持矩阵
- 记录编译配置的 --release、toolchain、source/target,以及多阶段构建最终镜像实际带入的 JRE/JDK
报错识别
java.lang.UnsupportedClassVersionError:
com/acme/App has been compiled by a more recent version
of the Java Runtime (class file version 61.0),
this version only recognizes class file versions up to 55.0Error: A JNI error has occurred, please check your installation and try again
Unsupported major.minor version 61.0
java.lang.IllegalArgumentException: Unsupported class file major version 61
NoClassDefFoundError: Could not initialize class com.acme.AppUnsupportedClassVersionError→ 运行期类加载失败,提示里会给出「需要的版本」和「当前上限」两个数字Error: A JNI error has occurred→ 启动器层的提示,紧跟其后的才是 UnsupportedClassVersionErrorUnsupported major.minor version→ 旧版 Java 的同义报错,数字规则完全相同Unsupported class file major version 61→ 构建或运行期的字节码工具(ASM、ByteBuddy、CGLIB)读不懂新版本NoClassDefFoundError / ExceptionInInitializerError→ 常是版本不兼容引发的次生错误,先回到最初的版本异常
class 文件版本对照:52.0=Java 8,55.0=Java 11,61.0=Java 17,65.0=Java 21。不要把 56.0 读成「Java 56」。
快速解法
升级运行时是首选;必须留在旧 JVM 时用 release 重新编译。
升级运行时(首选)— 改基础镜像,光改构建机没用
# Dockerfile
FROM eclipse-temurin:17-jre
COPY target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]必须留在旧 JVM — 用 release 重新编译
<properties>
<java.version>11</java.version>
</properties>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>11</release>
</configuration>
</plugin>报错类来自第三方依赖 — 先确认是哪个 JAR
mvn dependency:tree -Dincludes=com.example:legacy-lib验证
- 在最终部署镜像或容器内执行 java -version,确认启动脚本没有调用另一套 JRE
- 检查报错类所在 JAR 的 class major 与运行 JVM 上限,确认构建产物和镜像标签已更新
- 重新启动应用,确认不再出现 UnsupportedClassVersionError,并执行健康检查
解法无效时
- 容器内版本仍不符时,检查多阶段 Docker 构建的最终阶段、基础镜像、PATH、JAVA_HOME
- class 版本仍过高时,用 jar tf 定位报错类所属 JAR,再检查依赖树
- 版本兼容后仍启动失败时,以新的最深层异常继续分流
相关
- class file version 56.0 是 Java 12;61.0 是 Java 17,55.0 是 Java 11
- Unsupported major.minor version 是较旧 Java 的类似版本错误;NoSuchMethodError 更偏向二进制 API 版本冲突
- 构建机、宿主机、容器镜像和容器运行时可能使用不同 Java;最终以真正执行启动命令的 JVM 为准
当前环境
先记录 Spring Boot 版本、配置加载方式、启动命令、实际激活的 Profile、运行位置及外部配置版本。容器运行时要核验容器内环境变量、挂载文件和启动参数。
- 列出键的预期来源:application.yml、application-{profile}.yml、spring.config.import、环境变量或配置中心
- 区分普通配置、必需密钥和业务可选项;密钥不应通过默认值兜底
报错识别
Caused by: java.lang.IllegalArgumentException:
Could not resolve placeholder 'app.secret' in value "${app.secret}"
# 常见外层:Bean 创建失败或应用启动失败
UnsatisfiedDependencyException: Error creating bean with name 'appRunner'Could not resolve placeholder 'app.retry-count'
Failed to bind properties under 'app' to com.acme.AppProps
No spring.config.import property has been defined
Config data location 'configserver:http://config-host:8888' does not exist
Unable to load config data from 'file:./config/application-prod.yml'Could not resolve placeholder→ 当前生效来源里没有这个键,属于「找不到」Failed to bind properties under '...'→ 键存在但类型、格式或校验不通过,属于「值不对」No spring.config.import property has been defined→ 用了配置中心却漏写导入声明Config data location ... does not exist→ 导入路径写错或远端不可达Unable to load config data from→ 文件不存在、挂载路径或权限错误
检索方式:搜索 Could not resolve placeholder 并记下引号内的键名。APPLICATION FAILED TO START 和 IllegalArgumentException 都是外层包装。
快速解法
把异常里的键名记下来,按它的真实来源改对应位置。
键只在别的 Profile 里 — 激活那个 Profile
java -jar app.jar --spring.profiles.active=prod键应由配置中心下发 — 补导入声明
spring:
config:
import: optional:configserver:http://config-host:8888键确实没定义 — 必需值走环境变量,可选值才给默认
app:
secret: ${APP_SECRET} # 必需:由密钥管理系统注入
retry-count: ${APP_RETRY:3} # 可选:业务确认后才给默认验证
- 确认目标 PropertySource 已加载;只验证键存在,不输出敏感值
- 启动时显式记录非敏感的 Profile、配置文件路径和导入状态
- 重启后确认原占位符异常消失,并通过对应 Bean 的健康检查验证
解法无效时
- 键已存在仍无法解析时,检查配置键命名规则、环境变量映射、Profile 优先级
- 外部文件未生效时,检查容器挂载路径、文件权限、spring.config.import 是否可选
- 原异常消失但启动仍失败时,沿异常链定位新的根因
相关
- Failed to bind properties 更偏向键存在但类型不通过;Could not resolve placeholder 偏向键不存在
- Profile 未激活、配置中心不可达、文件权限错误和启动参数覆盖都可能造成同一占位符表象
- spring.config.import 缺失与配置值为空不是同一问题
当前环境
多发生在 Spring Cloud 与 Spring Boot 版本拍脑袋组合、或引入老第三方 starter 的场景。核心动作是查官方版本矩阵与依赖树。
- 确认 Spring Boot 与 Spring Cloud 的实际版本组合是否在官方支持矩阵内
- 记录是否手动覆盖过传递依赖的版本号
报错识别
Exception in thread "main" java.lang.NoSuchMethodError:
org.springframework.boot.SpringApplication.run(Ljava/lang/Object;[Ljava/lang/String;)
Lorg/springframework/context/ConfigurableApplicationContext;java.lang.NoClassDefFoundError: org/springframework/core/OrderProvider
java.lang.ClassNotFoundException: org.springframework.cloud.context.config.ConfigContext
NoSuchMethodError: 'void com.google.common.base.Preconditions.checkState(boolean)'NoSuchMethodError 指向框架内部类→ 运行时加载的 JAR 版本与编译时不一致NoClassDefFoundError / ClassNotFoundException→ 传递依赖被 omitted 或 exclude 掉堆栈指向业务代码而非框架→ 不是版本冲突,是代码问题,不走这条分支
快速解法
按官方版本矩阵对齐组合,用 dependencyManagement 锁定版本。
查 Spring 官方版本矩阵对齐组合
# 矩阵:https://spring.io/projects/spring-cloud#learn
# 如 Boot 3.5.x ↔ Cloud 2025.0.x用 dependencyManagement 锁定冲突版本
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
</dependency>
</dependencies>
</dependencyManagement>验证
- 执行 mvn dependency:tree 确认冲突版本已消除
- 重新启动应用,确认 NoSuchMethodError 消失
- 执行覆盖目标依赖的健康检查验证修复
解法无效时
- 仍报方法找不到时,用 mvn dependency:tree -Dverbose 检查 omitted for conflict
- 确认 BOM 版本与实际引入的 starter 版本匹配
- 排除冲突后仍失败时,沿异常链定位新的根因
相关
- NoSuchMethodError 是二进制不兼容;ClassNotFoundException 是类路径缺包
- 手动覆盖传递依赖版本是常见冲突来源
- Spring Cloud 与 Spring Boot 有严格的版本对应关系,不能拍脑袋组合
当前环境
先记录 Spring Boot 版本、内嵌服务器(Tomcat/Jetty/Undertow)、运行方式(IDE、JAR、服务或容器)、实际端口和绑定地址。容器场景要分别核对容器内监听端口、宿主机映射端口与同一网络命名空间中的进程。
- 确认实际生效的 server.port、server.address、启动参数和环境变量
- 记录部署副本数、滚动发布状态、探针端口及代理转发配置
报错识别
APPLICATION FAILED TO START
Description:
Web server failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that's listening on port 8080
Caused by: java.net.BindException: Address already in usejava.net.BindException: Address already in use: bind
java.net.BindException: Permission denied
java.net.BindException: Cannot assign requested address
Failed to start connector [Connector[HTTP/1.1-8080]]
org.apache.catalina.LifecycleException: Protocol handler start failedPort 8080 was already in use→ 端口被占用,最常见。Action 行只是建议,不是根因Address already in use→ 绑定失败根因,要和端口号一起看Permission denied→ 低端口(<1024)权限或安全策略,不是端口冲突Cannot assign requested address→ server.address 填的不是本机网卡地址LifecycleException / Failed to start connector→ Tomcat 连接器启动失败的外层包装,根因在 Caused by
快速解法
二选一:停掉占用者,或换端口。命令可直接复制,把端口和 PID 换成日志里的实际值。
查占用者
netstat -ano | findstr :8080 # Windows
ss -ltnp '( sport = :8080 )' # Linux
lsof -nP -iTCP:8080 -sTCP:LISTEN # macOS确认是废弃进程后结束它
kill -9 <PID> # Linux / macOS
taskkill /F /PID <PID> # Windows或换端口启动
server:
port: 8081验证
- 重新查询目标端口,确认旧 PID 已退出且没有非预期进程监听
- 启动应用后检查启动摘要中的端口和绑定地址
- 容器部署要同时检查容器监听、宿主机映射、Service/Ingress 和探针状态
解法无效时
- 未查到监听者时,核对应用是否在不同网络命名空间、IPv4/IPv6 地址或受保留端口上绑定
- 仍报冲突时,比较实际启动参数与日志端口,检查重复启动、同一应用多副本
- 容器内可执行 ss -ltnp 或检查进程列表,并对照编排平台的端口映射
相关
- Connection refused 是访问端没有服务监听,不等于启动时端口占用
- Permission denied 可能是低端口权限或安全策略,不是端口冲突
- 随机端口、代理端口、管理端口与业务端口可能不同;改业务端口不能自动修复探针或反向代理
当前环境
先记录 Spring Boot 版本、JDBC 驱动、数据库类型与版本、运行方式,以及应用实际连接的主机、端口、数据库名和网络位置。
- 确认应用是否应使用外部数据库、嵌入式数据库,或本来就不需要数据源
- 列出配置来源:配置文件、Profile、环境变量、启动参数、配置中心
报错识别
Failed to configure a DataSource: 'url' attribute is not specified
and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver classCaused by: java.net.ConnectException: Connection refused
Caused by: java.net.UnknownHostException: db-host
Caused by: java.sql.SQLException: Access denied for user 'app'@'10.0.0.5'
HikariPool-1 - Exception during pool initializationurl attribute is not specified→ 配置或驱动缺失,属于配置问题Connection refused→ 目标端口没有接受连接,数据库未启动或监听地址不对UnknownHostException→ DNS 或服务名解析失败Access denied for user→ 凭据、库名或授权范围错误Exception during pool initialization→ 连接池的外层包装,根因在它后面
快速解法
对号入座改一处即可,先看最深层 Caused by 是哪一类。
报 url attribute is not specified — 补驱动依赖和配置
spring:
datasource:
url: jdbc:mysql://db-host:3306/app?serverTimezone=Asia/Shanghai
username: ${DB_USER}
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver报 Connection refused — 先打通网络
nc -vz db-host 3306 # 在应用所在主机/容器内执行
ss -ltnp | grep 3306 # 数据库侧确认监听 0.0.0.0只想快速跑通 — 引入 H2 内存库
spring:
datasource:
url: jdbc:h2:mem:testdb验证
- 在实际运行环境检查配置键是否存在但不输出密码
- 从同一容器/主机用数据库客户端或 TCP 测试目标主机端口
- 重启应用,确认连接池初始化完成且没有新的 Caused by
解法无效时
- 仍提示未指定 URL 时,检查配置文件是否被打包、Profile 是否激活
- URL 已解析但连接失败时,沿应用容器到数据库的路径检查 DNS、路由、防火墙
- 网络可达但登录失败时,核对实际注入的用户名、密码、数据库名、权限
相关
- url attribute is not specified 是配置问题;Connection refused 是目标端口没有接受连接
- UnknownHostException 偏向 DNS;Connect timed out 偏向网络路径或防火墙
- 排除 DataSourceAutoConfiguration 只适用于业务明确不需要数据源的应用
当前环境
spring.datasource.url 已配置,但连库被拒或网络不通。三种成因对应三种报错文案,先把文案对上再动手。
- 确认数据库服务是否已启动并监听预期端口
- 确认应用与数据库是否在同一网络可达范围内
报错识别
java.sql.SQLInvalidAuthorizationSpecException:
Access denied for user 'root'@'localhost' (using password: YES)Caused by: java.net.CommunicationsException: Communications link failure
Caused by: java.net.ConnectException: Connection refused
Caused by: java.sql.SQLNonTransientConnectionException: Could not create connection to database server.
The server time zone value 'CST' is unrecognized or represents more than one time zoneAccess denied for user→ 凭据错误,核对 username / passwordCommunications link failure→ 库没起或网络不通Connection refused→ 目标端口没有接受连接time zone value ... is unrecognized→ 时区配置错误,需加 serverTimezone 参数
快速解法
按报错文案对号入座。
报 Access denied — 核对凭据
mysql -h localhost -u root -p8.x 驱动类名 + 时区参数
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_db?useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver验证
- 用相同凭据从应用所在主机直连数据库,确认可以登录
- 确认 driver-class-name 与 MySQL 版本匹配(8.x 用 com.mysql.cj.jdbc.Driver)
- 重启应用,确认连接池初始化成功
解法无效时
- 命令行能连但应用不行时,检查应用使用的 JDBC URL 参数和驱动版本
- 网络层面用 telnet 或 nc 测试端口可达性
- 确认数据库用户授权范围是否包含应用来源 IP
相关
- Access denied(凭据错)与 Failed to configure a DataSource(没配)是两种报错,别混着排查
- Communications link failure 可能是防火墙、安全组或数据库未启动
- 时区错误只加 serverTimezone 参数即可,不要改数据库或系统时区
当前环境
先记录 Spring Boot/Spring Framework 版本、扫描根包、激活 Profile、条件装配开关和模块依赖。
- 确认失败点是组件扫描、条件注册、限定符匹配,还是已注册 Bean 初始化失败
- 记录是否配置过 spring.main.allow-circular-references
报错识别
APPLICATION FAILED TO START
Description:
Field userService in com.example.controller.UserController required
a bean of type 'com.example.service.UserService' that could not be found.
Action:
Consider defining a bean of type 'com.example.service.UserService' in your configuration.No qualifying bean of type 'com.acme.Service' available:
expected at least 1 bean which qualifies as autowire candidate
NoUniqueBeanDefinitionException: expected single matching bean but found 2
UnsatisfiedDependencyException: Error creating bean with name 'orderController'expected at least 1 bean→ 缺候选:类没被扫描到、被 @Conditional/@Profile 排除expected single matching bean but found 2→ 候选歧义,提示里会列出全部候选名required a bean of type ... that could not be found→ 容器里没有调用方要的 Bean
快速解法
报错已指明哪个类、哪个字段缺依赖。按成因对症修复。
缺候选 — 给类补上构造型注解
@Service
public class UserService { ... }类在主包之外 — 扩大扫描范围
@SpringBootApplication(scanBasePackages = "com.example")
public class Application { ... }多个候选 — @Primary 定默认,@Qualifier 指定
@Service
@Primary
public class AlipayService implements PayService { }
@RestController
public class OrderController {
private final PayService payService;
public OrderController(@Qualifier("wechatService") PayService payService) {
this.payService = payService;
}
}验证
- 开启 Spring 条件评估报告,确认目标 Bean 是否注册
- 启动应用后检查原 Bean 异常已消失
- 执行覆盖目标依赖的健康检查验证修复
解法无效时
- 缺候选仍存在时,沿注入点的实际类型、泛型、限定符检查
- 确认类在运行时产物中且没有被 Profile/Condition 排除
- Bean 已注册但仍失败时,读取其最深层初始化异常
相关
- NoUniqueBeanDefinitionException 是多个候选歧义,不是 Bean 缺失
- 构造器注入相互引用走 E3 循环依赖分支
- 类加载失败、配置绑定失败可能发生在 Bean 创建阶段,不应直接归类为扫描问题
当前环境
成因唯一:两个配置或组件定义了同名 Bean。定位就是全局搜这个名字,看定义处。
- 确认是否有多处 @Bean 方法或同名 @Service 类
- 检查是否有重复的 @ComponentScan 把同一个类扫了两遍
报错识别
Description:
The bean 'fooService', defined in class path resource [com/example/config/FooConfig.class],
could not be registered. A bean with that name has already been defined in class path
resource [com/example/config/LegacyFooConfig.class] and overriding is disabled.could not be registered ... already been defined→ 同名 Bean 定义了两次,属于注册冲突提示里列出两个定义位置→ 按提示找到两处定义,删一处或改名
快速解法
删除重复定义,或给其中一个改名。
删除重复定义,或给其中一个改名
@Bean("fooServiceX")
public FooService fooServiceX() { ... }或用条件注解避免重复注册
@Bean
@ConditionalOnMissingBean
public FooService fooService() { ... }验证
- 全局搜 Bean 名确认只剩一处定义
- 重启应用确认原异常消失
- 确认功能正常且没有因改名导致注入断裂
解法无效时
- 只有一处定义仍报重复时,检查重复 @ComponentScan
- 确认是否通过 XML 配置和注解同时定义了同一个 Bean
- 检查测试配置类是否意外被生产环境加载
相关
- allow-bean-definition-overriding=true 只是掩盖问题,不是修复
- Boot 2.1 起默认禁止 Bean 覆盖,老项目升级时常遇到
- @ConditionalOnMissingBean 是自动配置的标准做法,业务代码中少用
当前环境
先确认是「真的成环」还是「创建期间二次引用」。不要先猜,按序排查。
- 记录是否配置过 spring.main.allow-circular-references
- Boot 2.6 起默认禁止循环引用,不要把它当作永久解决方案
报错识别
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| orderService
↑ ↓
| inventoryService
└─────┘BeanCurrentlyInCreationException: Error creating bean with name 'aService':
Requested bean is currently in creation: Is there an unresolvable circular reference?
The dependencies of some of the beans in the application context form a cycleform a cycle 并画出依赖环图→ 循环依赖,环上的 Bean 名就是要拆的最小闭环Requested bean is currently in creation→ 循环初始化的底层提示,仍需结合完整链确认@PostConstruct 里再取依赖→ 创建期间二次引用,不是真正的环
快速解法
治本:重构解耦。急着上线才用 @Lazy 打破构造器闭环。
用 @Lazy 在注入点断环
@Service
public class AService {
private final BService bService;
public AService(@Lazy BService bService) {
this.bService = bService;
}
}仅过渡:放开循环依赖限制(2.6+ 默认禁止)
spring:
main:
allow-circular-references: true验证
- 启动应用确认循环依赖异常消失
- 确认关键 Bean 完成初始化且没有延迟到首次请求才报错
- 执行覆盖目标依赖的健康检查
解法无效时
- 按错误中列出的 Bean 名称逐条画出依赖边,定位最小闭环
- 不要只切换字段注入或全局打开循环引用
- 打开 Bean 创建 DEBUG 日志确认是成环还是创建期间二次引用
相关
- Boot 2.6 起默认禁止循环引用;临时设置允许循环可能让应用启动,却保留运行时风险
- 字段注入可以绕过构造器循环,但不推荐作为解决方案
- BeanCurrentlyInCreationException 常与循环初始化相关,但仍需结合完整异常链确认
当前环境
成因是文件编码与控制台编码不一致,不是代码错误。按序确认 JVM 与终端两边。
- 确认 JVM 文件编码设置
- 确认终端或 IDE 控制台编码设置
报错识别
2026-09-23 10:32:11.312 INFO 8301 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat 8080 (http)中文变成问号或乱码方块→ 文件编码与控制台编码不一致日志结构正常、只有中文乱码→ 不是代码错误,是编码配置问题
快速解法
统一 JVM 与终端的编码为 UTF-8。
JVM 启动参数统一 UTF-8
java -Dfile.encoding=UTF-8 -jar app.jar确认终端编码(macOS/Linux)
locale | grep LANG
# 若不是 UTF-8,改终端设置验证
- 执行 java -XshowSettings:properties -version 确认 file.encoding 为 UTF-8
- 重启应用确认日志中文正常显示
- IDE 控制台需检查 Editor → File Encodings 三处都设 UTF-8
解法无效时
- JVM 编码正确但终端仍乱码时,检查终端本身的编码设置
- Windows 环境检查 chcp 输出,应为 65001
- Docker 容器内检查 LANG 环境变量
相关
- 乱码不代表代码错,别动业务代码
- 乱码伴随堆栈与 APPLICATION FAILED TO START 时,先修那个报错
- 不同操作系统默认编码不同:macOS 通常 UTF-8,Windows 默认 GBK
当前环境
启动期 OOM 基本是堆上限太小而不是泄漏。先看上限,再决定要不要深挖。
- 确认当前 JVM 堆上限配置
- 记录应用依赖数量和类加载量
报错识别
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: Requested array size exceeds VM limitJava heap space→ 堆上限太小,依赖很重Metaspace→ 类元数据区不够,加载的类太多GC overhead limit exceeded→ GC 花了大量时间但回收很少,本质也是内存不足
快速解法
调大堆上限。
启动时调大堆
java -Xmx1024m -jar app.jar同时调整初始堆和最大堆
java -Xms256m -Xmx2048m -jar app.jar验证
- 执行 java -XX:+PrintFlagsFinal -version 确认 MaxHeapSize 已更新
- 重启应用确认不再出现 OOM
- 观察启动过程 GC 日志确认内存使用正常
解法无效时
- 已调大堆仍 OOM 时,用 -XX:+HeapDumpOnOutOfMemoryError 导出堆转储
- 用 MAT 或 VisualVM 分析堆转储,查找大对象
- 检查是否有超大配置文件或依赖树异常膨胀
相关
- 启动期 OOM 与运行期 OOM 不是一个性质:前者基本是上限太小,后者才值得怀疑泄漏
- Metaspace OOM 需要调 -XX:MaxMetaspaceSize,不是 -Xmx
- 容器环境注意检查 cgroup 内存限制是否比 JVM 堆上限更小
当前环境
排除端口与内存后,剩下的多与系统资源(线程数、文件句柄)有关。先看完整 Caused by 链再对症。
- 确认已排除端口占用(C1)和 OOM(X2)
- 记录操作系统文件句柄上限和线程数限制
报错识别
org.springframework.context.ApplicationContextException: Unable to start web server;
nested exception is org.springframework.boot.web.server.WebServerException:
Unable to start embedded TomcatCaused by: java.io.IOException: Too many open files
Caused by: java.lang.OutOfMemoryError: unable to create new native thread
Caused by: java.net.SocketException: Too many open filesToo many open files→ 文件句柄上限不足unable to create new native thread→ 线程数达到系统上限Unable to start embedded Tomcat 无具体 Caused by→ 需要看更完整的日志上下文
快速解法
调大系统资源上限后重试。
调大文件句柄上限
ulimit -n 65535永久生效 — 编辑 /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535验证
- 执行 ulimit -n 确认句柄上限已调大
- 重启应用确认 Unable to start embedded Tomcat 消失
- 确认 Tomcat 正常监听端口并响应请求
解法无效时
- 查看完整 Caused by 链,若根部是 NPE / ClassNotFound → 是 Tomcat 装配问题
- 容器环境检查 cgroup 的线程数限制(pids.max)
- macOS 检查 launchctl limit maxproc 和 maxfiles
相关
- 高并发场景下先怀疑环境与资源,再怀疑代码
- Too many open files 也可能在运行期出现,需要检查连接池和文件流是否正确关闭
- Docker 容器默认 ulimit 可能偏小,需在 docker run 或 compose 中显式设置
