tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础与面向对象

  • 高级进阶

    • Java中的集合
    • Java泛型
    • Java中的日志
    • Java8-行为参数化与Lambda
    • Java8-函数式数据处理
    • Java8-代码优化与设计模式
    • Java8-新的日期和时间API
    • Java中的引用
    • Java中的垃圾回收
      • 垃圾回收
        • 前言
        • finalize方法
        • 它是什么
        • 设计初衷
        • 本地方法
        • 为什么后来被废弃?
        • 总结与推荐
        • GC运行时期
        • GC的作用和机制
        • 何时回收
        • 总结
  • 并发合集

  • JVM合集

  • 实战细节与其他

  • 代码之丑与提升

  • 《Java》学习笔记
  • 高级进阶
EffectTang
2025-11-02
目录

Java中的垃圾回收

# Java中的垃圾回收

# 垃圾回收

# 前言

“程序员都了解初始化的重要性,但常常会忘记同样也重要的清理工作。毕竟,谁需要清理一个int呢?但在使用程序库时,把一个对象用完后就“弃之不顾”的做法并非总是安全的(因为它会始终占据内存,造成内存泄漏)。

当然,Java有垃圾回收器负责回收无用对象占据的内存资源。但也有特殊情况:假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放该对象的这块“特殊”内存。

为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。”

上述这段话是来自《Java编程思想》,那么什么叫“垃圾回收器只知道释放那些经由new分配的内存”,还有其他分配的类型吗?

这所谓的finalize(),又是什么?

垃圾回收的过程是什么样的,它是怎么判断哪些对象需要回收的?

下面就一起来看看吧。

# finalize方法

finalize() 是 Java 早期设计中一个比较“理想化但失败”的机制 —— 它确实和本地方法(native code)有关系,但现在已经被官方明确标记为过时(deprecated)。

# 它是什么

finalize() 是 java.lang.Object 类中的一个方法:

protected void finalize() throws Throwable { }
1

它的作用是:

当一个对象**即将被垃圾回收(GC)*时,JVM 会在释放该对象的内存前*调用一次 finalize() 方法。

简单说:

  • 它是一个对象“临终前的回调函数”。

# 设计初衷

提供一个“最后的机会”来:

  • 释放非 Java 堆资源(如文件句柄、网络连接、本地内存等)
  • 执行清理逻辑

在 Java 早期(90年代),设计者想模仿 C++ 的析构函数(destructor),让开发者能在对象销毁时清理资源:

public class FileHandler {
    private FileDescriptor fd;

    @Override
    protected void finalize() throws Throwable {
        try {
            if (fd != null) {
                close(); // 关闭文件句柄
            }
        } finally {
            super.finalize(); // 保证父类也能清理
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 本地方法

之前说它和本地方法有关,什么叫本地方法?

在 Java 中,本地方法(Native Method) 是指 由非 Java 语言(通常是 C 或 C++)实现的方法,其具体实现代码位于 Java 虚拟机(JVM)外部。Java 通过 JNI(Java Native Interface,Java 本地接口) 机制来调用这些本地方法。

下面是本地方法的特征:

  1. 使用 native 关键字声明
  2. 只有方法声明,没有方法体(以分号 ; 结尾)
  3. 实际实现在 JVM 外部(如动态链接库 .dll、.so、.dylib 中)

在 Java 中,一些系统级资源是通过 JNI(Java Native Interface)调用 C/C++ 实现的本地代码管理的,例如:

  • 文件句柄(FileDescriptor)
  • 网络 Socket
  • 图形资源(窗口句柄)
  • 操作系统级内存分配

这些资源不是 JVM 自己分配的内存,所以 GC 无法自动回收。

为什么需要本地方法?

场景 说明
访问操作系统底层功能 如文件系统、硬件设备、系统调用(Java 标准库未封装的部分)
提升性能 对计算密集型任务(如图像处理、加密算法),C/C++ 比 Java 更高效
复用已有 C/C++ 代码 无需重写,直接集成遗留系统或高性能库(如 OpenCV、FFmpeg)
实现 Java 无法直接完成的操作 如直接内存操作、特定 CPU 指令等

# 为什么后来被废弃?

finalize() 的设计看起来优雅,但在实践中带来了严重问题:

1.不确定性(最大问题)

  • GC 什么时候运行?你无法预测。
  • 也就是说:finalize() 什么时候被调用,不可控。
  • 有时甚至永远不会被调用(程序在 GC 前就结束了)。

2.性能问题

  • 为了支持 finalize(),JVM 必须做额外处理:把带有 finalize() 的对象放入一个“终结队列”,延迟释放。
  • 这会拖慢 GC 效率。

3.可能导致对象“复活”

  • 在 finalize() 方法中,如果你让这个对象重新被引用,就会“复活”,导致 GC 无法释放它:

4.难以调试 & 资源泄漏风险大

因为调用时机不确定,很多资源可能迟迟不被释放(比如打开文件过多导致系统资源枯竭)。

它的调用流程是这样的:

对象创建 → 使用完毕 → 变成垃圾(不可达)
        ↓
第一次 GC 发现它是垃圾
        ↓
检查是否重写了 finalize()
        ↓ 是
放入 Finalizer 队列
        ↓
Finalizer 线程调用 finalize() ←──┐
        ↓                         │
(在 finalize() 中做资源清理)     │
        ↓                         │
本次 GC 结束                    │
        ↓                         │
第二次 GC 再次检查该对象         │
        ↓                         │
如果仍不可达 → 回收内存          │
如果可达(复活)→ 不回收 ────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

虽然机制如此,但:

  1. 你无法控制 GC 何时发生 → 资源可能长时间不释放
  2. Finalizer 线程优先级很低 → finalize() 可能延迟很久才执行
  3. 性能极差 → 对象回收需要至少两个 GC 周期
  4. Java 9+ 已废弃 → 未来可能移除

# 总结与推荐

一句话总结:

finalize() 是早期 Java 模仿 C++ 析构函数的尝试,用来清理 native 资源,但由于时机不可控、容易出错,已经被废弃,现代 Java 推荐使用 try-with-resources 来安全地释放资源。

历史演进时间线总结

阶段 行为 状态
Java 1.0–8 finalize() 存在并常用 ☑️ 设计用来释放本地资源
Java 9 被标记为 @Deprecated ⚠️ 不推荐使用
Java 18+ 建议使用 Cleaner 或 PhantomReference 替代 ✅ 更安全、可控

# GC运行时期

前面,为什么说gc运行无法控制?

在Java中,‌GC(Garbage Collection)指的是垃圾回收机制‌,它是Java虚拟机(JVM)自动管理内存的核心功能,用于检测和回收不再使用的对象所占用的内存空间,从而避免内存泄漏和程序崩溃

Java中的GC何时会去回收垃圾,通过时间判断吗?比如一个对象长时间没调用,就给回收掉?何时运行为什么无法预测?

它何时运行,不是通过时间判断的,而是 通过“可达性分析(Reachability Analysis)”判断对象是否还“活着”。

# GC的作用和机制

GC(Garbage Collector)负责做两件事:

  1. 自动识别不再被使用的对象(即“垃圾”);
  2. 回收它们所占用的内存空间,以便后续再利用。

识别:

那它如何进行可达性分析?什么是“可达”?

从一组称为 GC Roots 的对象出发,能通过引用链访问到的对象,就是可达的。

在 JVM 中,以下对象被视为 GC Roots(根节点):

GC Root 类型 举例说明
当前栈中引用的对象 局部变量、方法参数
方法区中类的静态引用 static 变量引用的对象
常量引用 被 final 修饰的常量对象
JNI(native 代码)引用 本地代码中持有的引用(JNI 全局引用)

我们用一个直观图解释:

GC Roots
   ↓
  A → B → C
        ↓
        D
   ↑
   X

还有一个孤立对象 Y

1
2
3
4
5
6
7
8
9
10
  • 从 GC Roots 出发,能访问到 A、B、C、D、X;
  • 但访问不到 Y —— 那么 Y 被认为“不可达”;
  • GC 就会认为 Y 是垃圾对象,等待回收。

那gc,在程序运行时,就是一直在不停的判断各种对象是否可达吗?还是说有时间间隔?发现不可达之后,就立即回收吗?

# 何时回收

Java 的垃圾回收(GC)并不是“一直在不停地判断所有对象是否可达”,也不是简单地“每隔固定时间扫描一次”。它的执行是事件驱动 + 分代策略 + 内存压力触发的,具有高度的按需性和阶段性。

GC 什么时候会触发?有人说,GC 通常会在内存不足或特定阈值时触发。这是正确的,但不完全。

触发时机由 JVM GC 策略(如 G1、ZGC、Shenandoah 等)决定,但通常包括以下情况:

触发场景 说明
堆内存不足 对象太多、分配空间不够,触发 GC
新生代(Eden 区)满了 Minor GC(快速、频繁)
老年代满了 Major GC 或 Full GC(慢但彻底)
显式调用 System.gc() 建议 GC(不保证立即执行)
元空间/方法区资源不足 特殊情况下触发 Full GC

# 总结

一句话总结:

Java 的 GC 不看时间,它看“引用关系”。

当一个对象不再被任何地方引用(不可达)时,它才会被判定为垃圾;而何时回收,由 JVM 自行决定。

上次更新: 2025/11/03, 15:45:22
Java中的引用
Java中创建线程的几种方式

← Java中的引用 Java中创建线程的几种方式→

最近更新
01
GitFlow的使用和注意
09-17
02
Spring中Bean的生命周期
09-03
03
数据不丢失与准确类
09-01
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式