垃圾回收会在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
相关产品推荐
相关产品推荐

