1. 概述
在《Thinking in Java》第五章 5.5 小节“清理:终结处理和垃圾回收”中,作者强调了对象清理工作的重要性。虽然 Java 有垃圾回收器(GC)处理 new 出来的内存,但对于“特殊内存区域”或特定的状态校验,仍需深入理解其内部机制。
2. finalize() 方法
垃圾回收器只知道释放通过 new 开辟的内存。针对特殊的内存空间,Java 提供了 finalize() 方法。
工作原理
- 一旦垃圾回收器准备好释放对象占用的内存,首先调用其
finalize()方法。 - 在下一次垃圾回收动作发生时,才会真正清理对象占用的内存。
finalize()是基类Object的方法,特殊场景下需重写。
核心注意事项
- 对象可能不被垃圾回收:如果 JVM 内存充足,可能永远不会触发 GC。
- 垃圾回收不等于“析构”:Java 不保证
finalize()一定执行,这与 C++ 的析构函数有本质区别。 - 垃圾回收只与内存有关:它的唯一目的是回收不再使用的内存资源。
3. finalize() 的特殊用法:终结条件验证
finalize() 并不适合作为通用的清理工具,但它可以用来验证对象的终结状态。例如,一个对象在被销毁前必须处于某种状态(如“已关闭”或“已清空”)。
示例代码:水箱(Tank)状态检查
/**
* @author plm
* @create 2021/2/16 22:40
*/
public class Tank {
private String status; // 状态可以是 empty 或者 full
public Tank() {}
public Tank(String status) {
this.status = status;
}
void checkIn() {
this.status = "empty";
}
@Override
protected void finalize() throws Throwable {
if ("full".equals(status)) {
System.out.println("Error: Tank is full. Cleanup not performed correctly!");
}
super.finalize();
}
}
class Test {
public static void main(String[] args) {
// 正常流程:状态被重置为 empty
Tank t = new Tank("full");
t.checkIn();
// 错误流程:创建一个 full 的 Tank 且没有 checkIn 就丢失了引用
new Tank("full");
// 强制垃圾回收器运行,尝试清理对象
System.gc();
}
}
运行结果:
Error: Tank is full. Cleanup not performed correctly!
4. 垃圾回收器算法
Java 虚拟机采用多种策略来管理内存。
4.1 引用计数 (Reference Counting)
- 定义:每个对象含有一个计数器。引用连接时 +1,失效时 -1。计数为 0 时释放。
- 缺陷:难以处理循环引用(对象间互相引用导致计数永不为 0)。
- 现状:并未应用于主流 Java 虚拟机中。
4.2 自适应技术 (Adaptive GC)
现代 JVM 会根据内存状况在不同的模式间切换:
1. 停止-复制 (Stop-and-Copy)
- 原理:暂停程序,将所有存活对象从当前堆复制到另一个堆,使内存紧凑排列。
- 缺点:需要双倍空间;当垃圾很少时,全量复制非常浪费。
2. 标记-清扫 (Mark-and-Sweep)
- 原理:从堆栈和静态存储区开始遍历,标记所有存活对象,然后统一清理未标记的对象。
- 缺点:清理后的内存空间不连续(碎片化),需要后续整理。
3. 自适应 (Adaptive)
JVM 会监视回收效率:
- 如果所有对象都很稳定,效率降低,则切换到标记-清扫模式。
- 如果内存碎片过多,则切换回停止-复制模式。
5. 性能优化:即时编译器 (JIT)
JIT 编译器能将 Java 字节码翻译成本地机器码,显著提升运行速度:
- 全量编译:花费时间长,增加代码长度。
- 惰性评估 (Lazy Evaluation):如 Java HotSpot 技术。只在必要时编译代码,且执行次数越多,优化程度越高(热点代码探测)。从不执行的代码则不会被 JIT 编译。