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 中顯式設置
