Java try-with-resources编译器实现解析:异常时资源关闭逻辑及反编译代码疑问
Great question—try-with-resources is one of those Java features that feels like black magic until you peek at how the compiler transforms it. Let’s break down your observations, fix the gaps in the decompiled code, and point you to better tools for this kind of deep dive.
First, Let’s Confirm the Execution Flow
Your test code’s output makes perfect sense:
- The main
tryblock runs, printing "trying" and throwing the main exception. - Resources are closed in reverse order (the last declared resource closes first):
CloseMeToocloses first, thenCloseMe. - Any exceptions thrown during close are added as suppressed exceptions to the main exception, which is why you can access them via
e.getSuppressed(). - Finally, the
catchblock runs, printing the main error and the suppressed ones.
Fixing the Incomplete Decompiled Code
The CFR decompile you got has two quirks:
- The unused
var2_4: This is an artifact of how CFR handles bytecode local variables—bytecode requires certain variable slots even if they’re unused in logical Java code, so CFR just spits it out as a placeholder. You can safely ignore it. - The missing exception chain logic: The decompiled code skips critical steps where closing exceptions are attached to the main exception. Here’s the corrected, fully accurate manual translation of what the compiler actually generates for your try-with-resources:
public class Test { public static void main(final String[] args) { System.out.println("Java version: " + System.getProperty("java.version") + "\n"); try { CloseMe me = new CloseMe(); Throwable primaryException = null; try { CloseMeToo meToo = new CloseMeToo(); Throwable secondaryException = null; try { System.out.println("trying"); throw new Exception("try failed"); } catch (Throwable tryBlockException) { secondaryException = tryBlockException; throw tryBlockException; } finally { // Close the second resource, handle its exceptions if (meToo != null) { if (secondaryException != null) { try { meToo.close(); } catch (Throwable closeException) { secondaryException.addSuppressed(closeException); } } else { meToo.close(); } } } } catch (Throwable middleException) { primaryException = middleException; throw middleException; } finally { // Close the first resource, handle its exceptions if (me != null) { if (primaryException != null) { try { me.close(); } catch (Throwable closeException) { primaryException.addSuppressed(closeException); } } else { me.close(); } } } } catch (Exception e) { System.out.println("failed"); System.out.println("\n"); System.out.println(e.getMessage()); System.out.println(e.getSuppressed()[0].getMessage()); System.out.println(e.getSuppressed()[1].getMessage()); } } }
Key Details in the Corrected Code:
- Each resource gets its own nested
try-finallyblock. - The
finallyblock is where resource closing happens—this runs before any outercatchblocks, which is how resources are closed before entering your maincatch. - If closing a resource throws an exception, it’s added to the main exception’s suppressed list using
addSuppressed()(instead of replacing the main exception).
Tools for Accurate Bytecode Inspection & Decompilation
If you want to see the exact compiler output without decompiler artifacts, try these:
javap(JDK Built-In)- Run
javap -c -v Test.classfrom your terminal. This dumps raw bytecode, including local variable tables, exception handlers, and opcode sequences. You’ll see the nestedfinallyblocks and suppressed exception logic directly in the bytecode.
- Run
- IntelliJ IDEA’s Built-In Decompiler
- Most modern IDEs (including IntelliJ) have excellent decompilers that handle try-with-resources far better than many online tools. Just open the compiled
.classfile in IntelliJ, and it’ll show you a clean, accurate decompilation (often even recognizing the original try-with-resources structure).
- Most modern IDEs (including IntelliJ) have excellent decompilers that handle try-with-resources far better than many online tools. Just open the compiled
- Procyon Decompiler
- Procyon is known for producing more accurate, readable decompiled code than many alternatives. You can run it via command line or use it as an IDE plugin.
The reason some decompilers spit out messy code is that try-with-resources is compiler syntactic sugar—it doesn’t exist in bytecode. Decompilers have to reverse-engineer the nested try-finally structure from raw bytecode, which isn’t always perfect.
内容的提问来源于stack exchange,提问作者the Hutt

