多态调用在垃圾回收进程中的表现与特殊性

作者:袖梨 2026-06-23
finalize()方法是Object类中用于对象回收前清理的机制,但调用时机不确定、不保证执行,且自Java 9起已被弃用;其核心用途是释放非内存资源(如文件句柄、JNI内存),而非替代try-with-resources等现代资源管理方式。

多态调用本身不直接参与垃圾回收,但它会影响对象的生命周期判断和 finalize() 方法的执行行为——尤其在涉及父类引用、子类构造与重写方法时,容易出现看似“矛盾”的日志输出(如 radius=0 后又变为 1),本质是构造顺序与多态调用时机共同作用的结果。

构造过程中多态调用引发的“提前绑定”

当子类构造器中调用 super() 时,父类构造器会先执行;若父类构造器中调用了被子类重写的方法(如 getString()),JVM 会按运行时类型(即子类)去调用该方法——哪怕子类字段(如 radius)尚未初始化。

  • 此时子类字段仍为默认值(int 为 0,Object 为 null),但方法已按子类逻辑执行
  • 例如:circle 构造中 super(a,b) 触发 point 构造,point 构造里调用 getString() → 实际执行 circle.getString() → 但 radius 还没赋值,输出 "radius=0"
  • 这是 Java 多态的底层机制决定的,不是 bug,而是规范行为

finalize() 中的多态行为与回收顺序

当对象进入垃圾回收队列并准备执行 finalize() 时,若该方法被子类重写,JVM 同样依据实际类型调用——这导致日志中看到的是子类的 finalize() 和其重写的 getString(),而非父类版本。

  • 注意:finalize() 已被标记为 deprecated(自 Java 9 起),不应作为资源清理主手段
  • GC 不保证 finalize() 一定执行,也不保证执行时机,更不保证执行次数(可能只执行一次,也可能不执行)
  • 上例中 circle finalizer 输出 radius=1/2,是因为此时对象已完全构造完毕,字段值有效

引用类型与可达性分析的隐含影响

多态常伴随向上转型(如 Point c1 = new Circle(...)),这种写法不影响 GC 判断——JVM 看的是对象实际内存是否还有强引用指向它,而不是变量声明类型。

  • c1 和 c2 都是父类引用,但指向的是子类实例;设为 null 后,只要无其他强引用,对象就满足“不可达”条件
  • System.gc() 只是建议 JVM 尽快回收,并不触发立即回收;finalize() 的调用时机由 GC 线程决定,且可能延迟
  • 即便存在多层继承链,只要整个对象图从 GC Roots 不可达,整块对象内存(含父类字段、子类字段)会被一并回收

开发中需规避的典型误区

多态 + 构造/回收组合容易引发理解偏差,实践中应避免以下做法:

  • 在父类构造器中调用可被重写的方法(易读取未初始化字段)
  • 依赖 finalize() 释放资源(应改用 try-with-resources 或 Cleaner)
  • 认为父类引用能“阻止”子类部分被回收(实际整个对象要么全活,要么全回收)
  • 混淆编译时类型(Point)与运行时类型(Circle)对方法调用的影响

相关文章

精彩推荐