StackTraces
Java已驗證

Java VerifyError 與 ClassFormatError 的區別

@debug_master32 天前更新閱讀 6 min00

這類錯幾乎跟你寫的業務程式碼無關:JVM 在真正執行一個類之前,會先校驗字節碼是否合法、類型是否符合安全規則。一旦某個 .class 是壞的、版本不匹配、或被 ASM/Lombok 等工具改壞,就會在校驗階段崩掉。報錯裡的類名是唯一的線索——它分不清問題在你的程式碼還是庫裡,但能告訴你先去查誰。

Issue & Env(案發現場與環境)

  • 現象:啟動時或首次加載某類時拋 VerifyError、ClassFormatError,或編譯/運行時報 Unsupported class file major version。
  • 環境:常出現在升級 JDK、引入字節碼增強(ASM / Javassist / ByteBuddy / Lombok / AOP)或依賴衝突之後。

StackTrace(堆疊日誌)

典型的校驗失敗:

java.lang.VerifyError: Stack map does not match the one at exception handler
	at org.springframework.cglib.proxy.Enhancer.setThreadCallbacks(Enhancer.java:158)

版本不匹配的線索在報錯文案裡:

java.lang.IllegalArgumentException: Unsupported class file major version 69
    at org.lombokweb.asm.ClassReader.<init>(ClassReader.java:200)
    at org.lombokweb.asm.ClassReader.<init>(ClassReader.java:187)
    at lombok.javac.handlers.HandleSneakyThrows.compile(HandleSneakyThrows.java:240)
    at lombok.javac.HandlerLibrary.handleAnnotation(HandlerLibrary.java:250)
    at lombok.javac.JavacAnnotationHandler.handle(JavacAnnotationHandler.java:157)
    at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1270)

Reproduce(復現程式碼)

把類聲明成 final,又讓 Spring 用 CGLIB 去給它做代理:

@Service
public final class LogService {   // final 无法被 CGLIB 继承
    public void log(String msg) {
        System.out.println(msg);
    }
}

或用一份老版本 Lombok 去編譯高版本 JDK 的程式碼,@SneakyThrows 處理階段在讀取 class 時直接拋版本不支持。

Root Cause(根因分析)

這兩兄弟的分工:ClassFormatError 是 .class 文件結構層面就壞,連解析都過不去;VerifyError 是結構能解析,但裡面的字節碼違反了 Java 的安全規則(棧類型對不上、跳轉目標非法等)。它們都是 JVM 在「加載 → 校驗」這步攔下來的,屬於 JVM 級 ERROR,不是普通 Exception。

來源集中在四類:

  1. 字節碼增強工具生成了非法字節碼——ASM、Javassist、ByteBuddy 用錯 API,或生成的棧映射(StackMapTable)和服務端字節碼不一致。
  2. 編譯產物版本高於運行 JVM——JDK 21 編譯的 class 在 JDK 8 上跑,class file major version 對不上。
  3. Lombok 等程式碼生成器與 JDK 版本不兼容——JDK 21 需要 Lombok 1.18.30 以上,老版本讀不了新 class 結構。
  4. ASM / CGLIB 等庫在依賴樹裡出現多個版本,Spring 用的 A 版本,字節碼卻按 B 版本生成,上下對不齊。

final 類被 CGLIB 代理失敗這類,本質也是字節碼生成層拿不到可繼承的父類,最終以校驗或生成例外的形式暴露出來。

The Fix(解決方案)

有一條主線:先看報錯裡的類名和具體信息,判斷問題是「版本錯位」還是「字節碼壞」。

拿不準是哪個 JAR 衝突,先看依賴樹:

mvn dependency:tree -Dverbose

版本不匹配就對齊:升級 Lombok(JDK 21 至少 1.18.30)或對應字節碼庫到與新 JDK 兼容的版本;把 class file 編譯目標降到運行 JVM 支持的 --release;ASM/CGLIB 混版本就統一到 Spring 所用的那個。

確定是字節碼壞,ASM 官方提供了一個校驗器,能精確指到是哪條指令出了錯:

java -cp "asm.jar:asm-tree.jar:asm-analysis.jar:asm-util.jar" \
    org.objectweb.asm.util.CheckClassAdapter \
    org/domain/package/YourClass.class

輸出會像 Error at instruction 2: Argument 1: expected Ljava/lang/String;, but found I 這樣,直接定位到出問題的指令。最後,無論哪種原因,改完都做一次乾淨構建 mvn clean package(或刪 target / build 重編),把舊的、可能還引用老字節碼的緩存清掉,避免「改完還報一樣的錯」。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法