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

垃圾回收会在addShutdownHook执行前删除对象?相关异常排查

问题根源分析与解决方案

首先,咱们来拆解你遇到的两个核心问题:

1. 构造函数执行期间终止程序导致的NullPointerException

你遇到的NPE,本质是对象引用的赋值时机晚于构造函数执行。Java中执行objectInstance = new Object(i)时,会先完成new Object(i)的整个构造过程(包括构造函数里的Thread.sleep(3000)),才会把对象引用赋值给objectInstance变量。

也就是说:

  • 当你在第一个对象的构造函数sleep期间终止程序时,objectInstance还保持着初始的null值
  • 此时shutdown hook的run()方法调用objectInstance.getTemporary(),自然会抛出NPE

2. 无法捕获最新创建的对象实例

同样是因为构造函数的耗时操作:当你在第三个对象的构造函数sleep时终止程序,第三个对象的构造还没完成,objectInstance还没被更新为这个新对象,仍然指向第二个已经构造完成的对象,所以shutdown hook只能打印前一个对象的信息。


针对性解决方案

方案1:调整对象初始化逻辑,提前赋值引用

把构造函数里的耗时操作移到单独的初始化方法中,先完成对象引用的赋值,再执行耗时操作:

修改自定义Object类(建议改名MyObject,避免和java.lang.Object冲突):

public class MyObject {
    private boolean exists = false;
    private int temporary;

    // 构造函数只做简单的成员变量赋值,不做耗时操作
    public MyObject(int temporary) {
        this.temporary = temporary;
    }

    // 单独的初始化方法,执行耗时逻辑
    public void init() {
        try {
            Thread.sleep(3000);
            exists = true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public boolean isExists() {
        return exists;
    }

    public int getTemporary() {
        return temporary;
    }
}

修改主类的对象创建逻辑:

public class Main {
    // 用volatile修饰,确保shutdown hook线程能看到最新的对象引用
    private static volatile MyObject objectInstance;

    static class Message extends Thread {
        public void run() {
            // 先判空,避免NPE
            if (objectInstance == null) {
                System.out.println("Object: default temporary=0, exists=false");
            } else {
                System.out.println("Object " + objectInstance.getTemporary() + " " + objectInstance.isExists());
            }
            System.out.println("Bye.");
        }
    }

    public static void main(String[] args) {
        try {
            Runtime.getRuntime().addShutdownHook(new Message());
            System.out.println("Program is starting...");
            for (int i = 0; i < 5; i++) {
                System.out.println("Next...");
                MyObject newObj = new MyObject(i);
                // 先把新对象的引用赋值给静态变量
                objectInstance = newObj;
                // 再执行耗时的初始化操作
                newObj.init();
            }
            System.out.println("Program is closing...");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样修改后:

  • 即使在init()的sleep期间终止程序,objectInstance已经指向了最新创建的对象,不会出现NPE
  • shutdown hook能捕获到当前正在初始化的对象实例,exists会是false(符合你的默认值需求)

方案2:在shutdown hook中增加判空逻辑(快速解决NPE)

如果暂时不想调整初始化逻辑,至少要在shutdown hook里先检查objectInstance是否为null,避免NPE:

static class Message extends Thread {
    public void run() {
        if (objectInstance != null) {
            System.out.println("Object " + objectInstance.getTemporary() + " " + objectInstance.isExists());
        } else {
            // 返回你预设的默认值
            System.out.println("Object: default temporary=0, exists=false");
        }
        System.out.println("Bye.");
    }
}

关于你的疑问解答

1. 应该用try{}catch{}finally{}还是addShutdownHook?

  • try{}catch{}finally{}只能处理正常流程中的异常退出,比如main方法执行到finally块。但如果是外部信号终止程序(比如Ctrl+C),finally块不一定会执行。
  • addShutdownHook是JVM层面的钩子,能捕获绝大多数程序终止场景(包括外部信号、System.exit()、JVM正常退出),更适合保存临界数据的场景。

所以你的场景下,addShutdownHook是更合适的选择,只要解决好变量可见性和赋值时机的问题即可。

2. 是否需要排查代码错误?

是的,你遇到的问题本质是对象初始化逻辑的时机问题,以及未处理静态变量的null情况,调整后就能解决。另外,自定义类名不要用Object,避免和Java核心类混淆,这是一个容易踩的坑。

内容的提问来源于stack exchange,提问作者Daniel Gannota

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:18:24