You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java try-with-resources编译器实现解析:异常时资源关闭逻辑及反编译代码疑问

Understanding try-with-resources: How Resources Are Closed Before Catch Blocks & Fixing Decompiled Code

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:

  1. The main try block runs, printing "trying" and throwing the main exception.
  2. Resources are closed in reverse order (the last declared resource closes first): CloseMeToo closes first, then CloseMe.
  3. Any exceptions thrown during close are added as suppressed exceptions to the main exception, which is why you can access them via e.getSuppressed().
  4. Finally, the catch block runs, printing the main error and the suppressed ones.

Fixing the Incomplete Decompiled Code

The CFR decompile you got has two quirks:

  1. 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.
  2. 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-finally block.
  • The finally block is where resource closing happens—this runs before any outer catch blocks, which is how resources are closed before entering your main catch.
  • 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:

  1. javap (JDK Built-In)
    • Run javap -c -v Test.class from your terminal. This dumps raw bytecode, including local variable tables, exception handlers, and opcode sequences. You’ll see the nested finally blocks and suppressed exception logic directly in the bytecode.
  2. 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 .class file in IntelliJ, and it’ll show you a clean, accurate decompilation (often even recognizing the original try-with-resources structure).
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 12:38:11