Java对象在new语句执行后需经类加载、内存分配、初始化(含严格初始化顺序)、引用建立才真正创建;初始化顺序为:父类静态块→子类静态块→父类实例变量赋值和实例块→父类构造器→子类实例变量赋值和实例块→子类构造器。

对象什么时候真正被创建出来
Java中对象不是在new语句执行完就立刻“活”了。它经历几个关键阶段:类加载(如果尚未加载)、内存分配、初始化(调用方法)、引用建立。其中最容易被忽略的是初始化顺序——父类静态块 → 子类静态块 → 父类实例变量赋值和实例块 → 父类构造器 → 子类实例变量赋值和实例块 → 子类构造器。
常见错误现象:NullPointerException出现在构造器里访问子类重写的方法,因为此时子类字段还没初始化,但父类构造器已调用该方法(多态提前触发)。
- 避免在构造器中调用可被重写的方法
- 静态字段初始化发生在类加载时,与对象无关;实例字段初始化发生在每次
new时 -
new操作本身不保证可见性,需配合final字段或同步机制确保安全发布
对象什么时候算“不可达”并进入回收候选
GC判定对象是否可回收,看它是否还存在从GC Roots可达的引用链。GC Roots包括:虚拟机栈/本地方法栈中的引用、方法区中的静态字段引用、常量引用、JNI引用。只要任意一条路径能走到该对象,它就不算“死”。
典型误判场景:
立即学习“Java免费学习笔记(深入)”;
- 局部变量还在作用域内(哪怕没再使用),对象仍被视为活跃
- 集合类(如
ArrayList)持有对象引用却不清理,造成内存泄漏 - 内部类隐式持有外部类引用,导致外部实例无法回收(可用
static内部类规避) - WeakReference/PhantomReference 不阻止回收,但SoftReference在内存不足时才回收
finalize()为什么不能当析构函数用
finalize() 方法在JDK 9起已被标记为@Deprecated,JDK 18后彻底移除。它既不保证执行时机,也不保证一定执行,更不保证执行次数(可能因JVM退出而跳过)。它的唯一历史用途是作为资源清理的“兜底”,但实际完全不可靠。
替代方案必须显式控制:
- 实现
AutoCloseable,配合try-with-resources(适用于文件、连接等) - 使用
Cleaner(JDK 9+)注册清理逻辑,比finalize更轻量、可预测 - 不要在
finalize里复活对象(比如把this赋给静态变量),这会导致严重性能问题且违反GC契约
对象从新生代到老年代的晋升条件
HotSpot VM默认采用分代回收,对象优先分配在Eden区。一次Minor GC后,存活对象进入Survivor区;多次GC后仍存活,则晋升至老年代。但晋升不是只看“年龄”:
- 对象大小超过
-XX:PretenureSizeThreshold会直接进老年代(避免大对象在Survivor间复制) - Survivor空间不足时,部分对象会“担保失败”直接进老年代(即使年龄不够)
- 动态年龄判定:如果某次Survivor中相同年龄所有对象大小总和 > Survivor空间一半,则大于等于该年龄的对象全部晋升
- 长期存活对象(如缓存、单例)应尽量设计为一开始就驻留老年代,减少GC压力
这些细节在高并发或大堆场景下极易成为瓶颈,但往往要等到Full GC频繁或停顿飙升时才被注意到。






