避免使用Finalizer和Cleaner机制-[创建和销毁对象]

本文探讨了Java中Finalizer和Cleaner机制的原理及应用,对比了它们与显式资源释放方法的区别,分析了各自的优缺点,以及在资源管理中的作用。

1.Finalizer机制

finalize是Object类中的一个方法, 当一个对象被销毁前jvm会去调用该对象的finalize方法。

@Deprecated(since="9")
protected void finalize() throws Throwable { } // 在java9中已被废弃

如果一个对象依赖了底层资源(例如Socket, Stream, Connection...)。在对象销毁前, 需要确保其占有的资源被释放出来, 一般都会去重写finalize来确保资源被关闭。例如下面的代码

public class SocketOperator {

    private final Socket socket;

    public SocketOperator(String host, Integer port) throws IOException {
        this.socket = new Socket(host, port);
    }

    public String readAsString() {
        // 读数据逻辑
        return null;
    }

    public String writeAsString(String msg) {
        // 写数据逻辑
        return null;
    }

    @Override
    protected void finalize() throws Throwable {
        // 关闭网络连接
        System.out.println("Release connection");
        socket.close();
    }

    public void close() throws IOException {
        socket.close();
    }
}

下面这段代码定义了声明了Socket操作的类, 这个对象依赖一个底层的Socket对象。在这个对象销毁时, 其所拥占有的Socket资源也应该被安全的释放。显式地声明了一个close方法来让用户调用用于释放资源。但有些时候, 客户端可能忘记了调用这个方法, 我们需要做一些操作来保证资源来被正常释放, 防止资源泄露, 这个时候就可以去重写finalize方法, 让他作为一个安全网(safety net),以防资源的拥有者忽略了它的close方法。

Finalizer机制线程的运行优先级低于其他应用程序线程,所以对象被回收的速度低于进入队列的速度。语言规范并不保证哪个线程执行Finalizer机制,因此除了避免使用Finalizer机制之外,没有轻便的方法来防止这类问题。

2.Cleaner机制

Cleaner机制使用起来有点棘手。下面是演示该功能的一个简单的Room类。假设Room对象必须在被回收前清理干净。Room类实现AutoCloseable接口;它的自动清理安全网使用的是一个Cleaner机制,这仅仅是一个实现细节。与Finalizer机制不同,Cleaner机制不污染一个类的公共API:

// An autocloseable class using a cleaner as a safety net
public class Room implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();

    // Resource that requires cleaning. Must not refer to Room!
    private static class State implements Runnable {
        int numJunkPiles; // Number of junk piles in this room

        State(int numJunkPiles) {
            this.numJunkPiles = numJunkPiles;
        }

        // Invoked by close method or cleaner
        @Override
        public void run() {
            System.out.println("Cleaning room");
            numJunkPiles = 0;
        }
    }

    // The state of this room, shared with our cleanable
    private final State state;

    // Our cleanable. Cleans the room when it’s eligible for gc
    private final Cleaner.Cleanable cleanable;

    public Room(int numJunkPiles) {
        state = new State(numJunkPiles);
        cleanable = cleaner.register(this, state);
    }

    @Override
    public void close() {
        cleanable.clean();
    }
}

静态内部State类拥有Cleaner机制清理房间所需的资源。 在这里,它仅仅包含numJunkPiles属性,它代表混乱房间的数量。 更实际地说,它可能是一个final修饰的long类型的指向本地对等类的指针。 State类实现了Runnable接口,其run方法最多只能调用一次,只能被我们在Room构造方法中用Cleaner机制注册State实例时得到的Cleanable调用。 对run方法的调用通过以下两种方法触发:通常,通过调用Room的close方法内调用Cleanable的clean方法来触发。 如果在Room实例有资格进行垃圾回收的时候客户端没有调用close方法,那么Cleaner机制将(希望)调用State的run方法。

一个State实例不引用它的Room实例是非常重要的。如果它引用了,则创建了一个循环,阻止了Room实例成为垃圾收集的资格(以及自动清除)。因此,State必须是静态的嵌内部类,因为非静态内部类包含对其宿主类的实例的引用。同样,使用lambda表达式也是不明智的,因为它们很容易获取对宿主类对象的引用。

就像我们之前说的,Room的Cleaner机制仅仅被用作一个安全网。如果客户将所有Room的实例放在try-with-resource块中,则永远不需要自动清理。行为良好的客户端如下所示:

public class Adult {
    public static void main(String[] args) {
        try (Room myRoom = new Room(7)) {
            System.out.println("Goodbye");
        }
    }
}

正如你所预料的,运行Adult程序会打印Goodbye字符串,随后打印Cleaning room字符串。但是如果时不合规矩的程序,它从来不清理它的房间会是什么样的?

public class Teenager {
    public static void main(String[] args) {
        new Room(99);
        System.out.println("Peace out");
    }
}

你可能期望它打印出Peace out,然后打印Cleaning room字符串,但在我的机器上,它从不打印Cleaning room字符串;仅仅是程序退出了。 这是我们之前谈到的不可预见性。 Cleaner机制的规范说:“System.exit方法期间的清理行为是特定于实现的。 不保证清理行为是否被调用。”虽然规范没有说明,但对于正常的程序退出也是如此。 在我的机器上,将System.gc()方法添加到Teenager类的main方法足以让程序退出之前打印Cleaning room,但不能保证在你的机器上会看到相同的行为。

Cleaner机制比Finalizer机制要好一些,因为Java类的创建者可以控制自己cleaner机制的线程,但cleaner机制仍然在后台运行,在垃圾回收器的控制下运行,但不能保证及时清理。

总结

1.不要相信System.gc和System.runFinalization方法。

他们可能会增加Finalizer和Cleaner机制被执行的几率,但不能保证一定会执行。 曾经声称做出这种保证的两个方法:System.runFinalizersOnExit和它的孪生兄弟Runtime.runFinalizersOnExit,包含致命的缺陷,并已被弃用了几十年[ThreadStop]。

2.在执行Finalizer机制过程中,未捕获的异常会被忽略,并且该对象的Finalizer机制也会终止 [JLS, 12.6]。

未捕获的异常会使其他对象陷入一种损坏的状态(corrupt state)。如果另一个线程试图使用这样一个损坏的对象,可能会导致任意不确定的行为。通常情况下,未捕获的异常将终止线程并打印堆栈跟踪( stacktrace),但如果发生在Finalizer机制中,则不会发出警告。Cleaner机制没有这个问题,因为使用Cleaner机制的类库可以控制其线程。

3.使用finalizer和cleaner机制会导致严重的性能损失。

使用finalizer机制创建和销毁对象的速度要慢50倍。 这主要是因为finalizer机制会阻碍有效的垃圾收集。 如果使用它们来清理类的所有实例,那么cleaner机制的速度与finalizer机制的速度相当,但是如果仅将它们用作安全网( safety net),则cleaner机制要快得多。

4.Finalizer和Cleaner机制有什么好处呢?

一个是作为一个安全网(safety net),以防资源的拥有者忽略了它的close方法。虽然不能保证Finalizer和Cleaner机制会迅速运行(或者根本就没有运行),最好是把资源释放晚点出来,也要好过客户端没有这样做。
第二种合理使用Cleaner机制的方法与本地对等类(native peers)有关。本地对等类是一个由普通对象委托的本地(非Java)对象。由于本地对等类不是普通的 Java对象,所以垃圾收集器并不知道它,当它的Java对等对象被回收时,本地对等类也不会回收。

<think> 嗯,用户想深入理解Kubernetes的垃圾回收机制Finalizer阻塞问题,明显是在准备高级运维或架构师岗位的面试。从ta连续追问的节奏看,应该已经掌握了基础概念,现在需要原理级的剖析。 用户提供的引用材料很有价值,特别是[2][4]明确了Finalizer的本质是删除拦截器,[3]的官方博客链接更是权威佐证。不过ta可能忽略了垃圾回收器(Garbage Collector Controller)在其中的协调作用——这正是需要重点串联的知识点。 垃圾回收机制的核心其实是三层联动: 1 OwnerReference链(负责标记依赖关系) 2 Finalizer队列(负责拦截删除) 3 GC控制器(负责遍历依赖树) 当用户发出kubectl delete时,关键事件流如下: ``` API Server收到删除请求 → 检查Finalizers字段 → 若存在则仅设置deletionTimestamp(进入Terminating状态) → GC控制器检测到Owner删除 → 级联删除依赖对象 → 各对象的控制器清理Finalizers → 对象真正消失 ``` 常见的Finalizer阻塞案例往往出现在: - 自定义控制器未实现Finalizer处理逻辑(比如忘了写Reconcile删除回调) - 跨命名空间的OwnerReference依赖(需要Cluster作用域的GC权限) - 存储资源残留(PV的Protection Finalizer典型如kubernetes.io/pv-protection) 面试时如果被问"Pod卡在Terminating状态怎么办",应该分三步作答: 1 用kubectl describe pod查Finalizers字段 2 检查对应控制器日志(比如可能是csi-driver未响应) 3 特殊情况下用kubectl patch强制移除Finalizer(需谨慎) 需要特别注意的陷阱题是:StatefulSet缩容时,由于每个Pod有独立PVC,GC必须等所有Pod的PVC清理完才会移除StatefulSet的Finalizer。如果存储插件故障,整个StatefulSet就会阻塞删除。 最后补充两个面试加分点: 1 解释--cascade=orphan的原理(直接清空OwnerReference而非触发GC) 2 列举内置Finalizer类型(如foregroundDeletion/namespace自动清理机制) </think> 以下是 Kubernetes 垃圾回收机制Finalizer 阻塞问题的深度解析,结合面试高频考点: --- ### 一、Kubernetes 垃圾回收机制 #### 1. **核心原理** 垃圾回收器(Garbage Collector Controller)通过**对象依赖关系树**管理资源生命周期: - **OwnerReference 机制**:子对象(如 Pod)通过 `metadata.ownerReferences` 字段标记其父对象(如 Deployment) - **级联删除策略**: ```yaml apiVersion: apps/v1 kind: Deployment metadata: deletionPolicy: Cascading # 默认策略(级联删除子对象) ``` - **孤儿删除策略**:手动保留子对象 ```bash kubectl delete deployment nginx --cascade=orphan # 子对象变为孤儿[^4] ``` #### 2. **垃圾回收流程** ```mermaid graph TD A[用户发起删除请求] --> B{是否设置 Finalizer?} B -->|是| C[设置 deletionTimestamp] B -->|否| D[直接删除对象] C --> E[GC 控制器检测 OwnerReference] E --> F[删除所有依赖的子对象] F --> G[移除 Finalizers 字段] G --> H[对象从 etcd 删除] ``` --- ### 二、Finalizer 阻塞问题深度剖析 #### 1. **Finalizer 工作原理** - **本质**:存储在对象 `metadata.finalizers` 中的拦截器队列 - **触发条件**:对象被删除时,若存在 Finalizer 则进入 `Terminating` 状态 - **解除阻塞条件**:控制器完成清理任务后移除对应 Finalizer #### 2. **典型阻塞场景与解决方案** | **场景** | **原因** | **解决方案** | |------------------------------|-----------------------------------|------------------------------------------------------------| | PVC/PV 卡在 Terminating | StorageClass 回收策略为 Retain[^5] | 手动清理存储数据后删除 PV | | 自定义控制器未处理 Finalizer | 控制器未实现删除回调逻辑 | 检查控制器日志并修复逻辑 | | 跨命名空间依赖阻塞 | NetworkPolicy 等资源跨 NS 引用 | `kubectl edit` 手动移除 Finalizer(需谨慎!) | | CRD 资源删除阻塞 | CRD 控制器 CrashLoop | 重启控制器或执行 `kubectl patch crd xxx --type merge -p '{"metadata":{"finalizers":[]}}'` | #### 3. **阻塞诊断命令** ```bash # 查看阻塞资源的 Finalizer kubectl get pod/<name> -o jsonpath='{.metadata.finalizers}' # 检查关联控制器日志(以 StatefulSet 为例) kubectl logs -l app=my-controller --tail=100 | grep "finalizer" # 强制移除 Finalizer(紧急措施) kubectl patch pod/<name> --type json --patch='[{"op": "remove", "path": "/metadata/finalizers"}]' ``` --- ### 三、面试级问题解析 #### **Q:为什么 PV 设置了 Retain 策略后会被 Finalizer 阻塞?** **A**:Retain 策略要求管理员手动清理存储资源,因此 PV 控制器添加 `kubernetes.io/pv-protection` Finalizer 阻止删除,直到确认存储数据已处理[^5]。这是防止数据丢失的关键设计。 #### **Q:如何设计自定义控制器的 Finalizer 机制?** **A**:需实现以下逻辑: 1. 对象创建时添加 Finalizer(如 `mycompany.com/finalizer`) 2. 监听 `deletionTimestamp` 字段变化 3. 执行前置清理(如释放外部资源) 4. 清理完成后移除 Finalizer ```go // 伪代码示例(基于 controller-runtime) func (r *MyReconciler) Reconcile() { if obj.DeletionTimestamp.IsZero() { if !containsFinalizer(obj) { addFinalizer(obj) updateObject(obj) } } else { releaseExternalResources(obj) // 清理逻辑 removeFinalizer(obj) updateObject(obj) } } ``` --- ### 四、实战排查案例 **问题**: Namespace 卡在 `Terminating` 状态无法删除 **诊断流程**: 1. 检查 Finalizer: ```bash kubectl get ns my-ns -o json | grep finalizers # 输出: "finalizers": ["kubernetes"] ``` 2. 发现是 Kubernetes 系统 Finalizer 阻塞 3. 检查残留资源: ```bash kubectl api-resources --verbs=list --namespaced -o name | xargs -n1 kubectl get -n my-ns ``` 4. 删除残留的 WebhookConfiguration 5. 系统自动移除 Finalizer ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值