Java 对象释放与 finalize 方法

简介: 关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。

本文谈论的知识很浅显,只是我发现自己掌握的相关知识并不扎实,对细节并不清楚,遂将疑惑解开,并记录于此。

按惯例先上结论,对如下知识点已经清楚的选手可以省下看本文的时间了。

结论

  1. 对象的 finalize 方法不一定会被调用,即使是进程退出前。

  2. 发生 GC 时一个对象的内存是否释放取决于是否存在该对象的引用,如果该对象包含对象成员,那对象成员也遵循本条。

  3. 对象里包含的对象成员按声明顺序进行释放。

证明

假设有以下类定义:

class A {
   
    public A() {
   
        System.out.println("A()");
    }

    protected void finalize() {
   
        System.out.println("~A()");
    }

    B b;
}

class B {
   
    public B() {
   
        System.out.println("B()");
    }

    protected void finalize() {
   
        System.out.println("~B()");
    }
}

结论 1 证明

main 方法中有如下代码:

A a = new A();
B b = new B();
a.b = b;
a = null;

输出是什么呢?

A()
B()

与我想象中的有些不一样,我以为至少在进程退出前 A 类对象和 B 类对象都会被释放掉的。

我们明确一下 finalize 方法的调用时机,引用官方 API 文档的解释:

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup.

也就是说,finalize 是在 JVM 执行 GC 的时候才会执行的,而很显然,在这个例子里 main 方法退出时并没有执行 GC,而 GC 是否执行以及其执行的时机并不是我们可以精确控制的,此即证明了结论 1

结论 2 证明

虽然我们不能精确控制 GC 的时机,但我们可以给 JVM 建议,比如我们在最后加个 System.gc() 建议 JVM 进行 GC。

A a = new A();
B b = new B();
a.b = b;
a = null;
System.gc();

现在输出变成了

A()
B()
~A()

可见 JVM 听从了我们的建议,执行了 GC,由于此时 A 类对象已经没有引用了,所以它被释放,而该对象的 B 类对象成员由于被局部变量 b 引用,此时不会释放。

而一个在 GC 时对象成员也会被释放的 A 类对象调用是怎么样的呢?

A a = new A();
a.b = new B();
a = null;
System.gc();

此时输出为

A()
B()
~B()
~A()

如上两段代码执行结果的对比证明了结论 2

另外需要说明的是,Runtime 类里有一个 runFinalizersOnExit 方法,可以让程序在退出时执行所有对象的未被自动调用 finalize 方法,即使该对象仍被引用。但是从官方文档可以看出,该方法已经废弃,不建议使用,引用官方 API 文档如下:

Deprecated. This method is inherently unsafe. It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects, resulting in erratic behavior or deadlock.

Enable or disable finalization on exit; doing so specifies that the finalizers of all objects that have finalizers that have not yet been automatically invoked are to be run before the Java runtime exits. By default, finalization on exit is disabled.

而同样是 Runtime 类里的 runFinalization 方法则在调用后并没有看到明显的效果,即如果不发生 GC,那即使调用了 runFinalization 方法,已经待回收的对象的 finalize 方法依然没有被调用。

结论 3 证明

我们修改一下几个类的定义:

class A {
   
    public A() {
   
        System.out.println("A()");
    }

    protected void finalize() {
   
        System.out.println("~A()");
    }

    B b;    // line a
    C c;    // line b
}

class B {
   
    public B() {
   
        System.out.println("B()");
    }

    protected void finalize() {
   
        System.out.println("~B()");
    }
}

class C {
   
    public C() {
   
        System.out.println("C()");
    }

    protected void finalize() {
   
        System.out.println("~C()");
    }
}

现在在 main 方法里有如下调用:

A a = new A();
a.b = new B();
a.c = new C();
a = null;
System.gc();

输出是

A()
B()
C()
~B()
~C()
~A()

而如果我们互换一下 A 类声明带注释的 line a 与 line b 的位置,即变成

C c;    // line b
B b;    // line a

输出变成

A()
B()
C()
~C()
~B()
~A()

此即证明了结论 3

目录
相关文章
|
5月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
774 157
|
5月前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
398 18
|
5月前
|
Java 编译器 Go
【Java】(5)方法的概念、方法的调用、方法重载、构造方法的创建
Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的优点使程序变得更简短而清晰。有利于程序维护。可以提高程序开发的效率。提高了代码的重用性。方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。这种就属于驼峰写法下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。
276 4
|
6月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
214 11
|
5月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
371 5
|
6月前
|
Java 开发者
Java 函数式编程全解析:静态方法引用、实例方法引用、特定类型方法引用与构造器引用实战教程
本文介绍Java 8函数式编程中的四种方法引用:静态、实例、特定类型及构造器引用,通过简洁示例演示其用法,帮助开发者提升代码可读性与简洁性。
|
7月前
|
算法 Java 开发者
Java 项目实战数字华容道与石头迷阵游戏开发详解及实战方法
本文介绍了使用Java实现数字华容道和石头迷阵游戏的技术方案与应用实例,涵盖GUI界面设计、二维数组操作、游戏逻辑控制及自动解法算法(如A*),适合Java开发者学习游戏开发技巧。
467 46
|
7月前
|
缓存 安全 Java
Java反射机制:动态操作类与对象
Java反射机制是运行时动态操作类与对象的强大工具,支持获取类信息、动态创建实例、调用方法、访问字段等。它在框架开发、依赖注入、动态代理等方面有广泛应用,但也存在性能开销和安全风险。本文详解反射核心API、实战案例及性能优化策略,助你掌握Java动态编程精髓。
|
8月前
|
Java 索引
Java ArrayList中的常见删除操作及方法详解。
通过这些方法,Java `ArrayList` 提供了灵活而强大的操作来处理元素的移除,这些方法能够满足不同场景下的需求。
690 30
|
8月前
|
安全 Java API
Java 17 及以上版本核心特性在现代开发实践中的深度应用与高效实践方法 Java 开发实践
本项目以“学生成绩管理系统”为例,深入实践Java 17+核心特性与现代开发技术。采用Spring Boot 3.1、WebFlux、R2DBC等构建响应式应用,结合Record类、模式匹配、Stream优化等新特性提升代码质量。涵盖容器化部署(Docker)、自动化测试、性能优化及安全加固,全面展示Java最新技术在实际项目中的应用,助力开发者掌握现代化Java开发方法。
360 1