You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中如何确保对象被GC时调用资源释放函数?

确保Native资源被正确释放的几种方案

针对你提出的场景——对象通过CompletableDeferred传递,接收方可能因超时/取消无法获取对象,导致native资源未被close的问题,这里有几种可靠的解决思路:

1. 给对象添加终接器作为兜底

虽然Java/Kotlin的终接器(finalize)执行时机不确定,但可以作为最后一道防线,避免资源永久泄漏:

class TestLink(val address: String) {
    val fd = openNativeResource(address)
    // ... 原有代码 ...

    @Suppress("deprecation")
    protected fun finalize() {
        closeNativeResource(fd)
    }
}

注意:终接器由GC触发,但无法保证执行时机,甚至可能在极端场景下被跳过,只能作为兜底手段,不能依赖它作为主要释放方式。

2. 将对象生命周期与创建协程绑定

在创建TestLink的协程中,用finally块确保不管协程是否被取消,都能处理已创建对象的资源释放:
先修改测试代码中的launch块:

val job = launch {
    var link: TestLink? = null
    try {
        link = TestLink.create(this, "test")
        linkDeferred.complete(link)
        // 协程挂起,直到被取消(若接收方拿到对象,后续释放由接收方负责)
        awaitCancellation()
    } catch (e: Exception) {
        linkDeferred.completeExceptionally(e)
    } finally {
        // 仅当对象已创建、且接收方未成功获取时,执行close
        link?.takeIf { !linkDeferred.isCompleted }?.close()
    }
}

同时给TestLink增加内部协程管理,确保close时能终止后台任务:

class TestLink(val address: String) {
    val fd = openNativeResource(address)
    private val _eventChannel = Channel<String>()
    val incoming: ReceiveChannel<String> get() = _eventChannel
    private var backgroundJob: Job? = null

    companion object {
        suspend fun create(scope: CoroutineScope, address: String): TestLink {
            val link = TestLink(address)
            link.start(scope)
            return link
        }
    }

    fun start(scope: CoroutineScope) {
        backgroundJob = scope.launch { 
            while (true) {
                val data = readNativeResource(fd)
                if (data != null)
                    _eventChannel.send(data)
                else
                    break
            }
            _eventChannel.close()
        }
    }

    fun close() {
        backgroundJob?.cancel()
        closeNativeResource(fd)
        _eventChannel.close()
    }
}

这样,当创建协程被取消时,finally块会自动检查并释放未被接收方接管的对象资源。

3. 给CompletableDeferred添加完成回调

利用invokeOnCompletion监听CompletableDeferred的所有状态(正常完成、异常、取消),针对性处理资源释放:

val linkDeferred = CompletableDeferred<TestLink>()
var createdLink: TestLink? = null

val job = launch {
    try {
        createdLink = TestLink.create(this, "test")
        linkDeferred.complete(createdLink!!)
    } catch (e: Exception) {
        linkDeferred.completeExceptionally(e)
    }
}

// 监听Deferred状态变化,处理资源
linkDeferred.invokeOnCompletion { cause ->
    // 因超时取消且对象已创建时,执行close
    if (cause is CancellationException && createdLink != null) {
        createdLink!!.close()
    }
}

val link = withTimeoutOrNull(100) {
    linkDeferred.await()
}

if (link != null) {
    var counter = 0
    for (data in link.incoming) {
        counter++
        if (counter >= 100) break
        println(data)
    }
    link.close()
} else {
    job.cancelAndJoin()
}

这种方式直接绑定Deferred的生命周期,一旦因超时取消,回调会立即触发并释放资源。

4. 实现AutoCloseable接口(处理接收方成功获取的场景)

让TestLink实现AutoCloseable,接收方可以用use函数自动管理资源:

class TestLink(val address: String) : AutoCloseable {
    // ... 原有代码 ...

    override fun close() {
        backgroundJob?.cancel()
        closeNativeResource(fd)
        _eventChannel.close()
    }
}

接收方使用时:

val link = withTimeoutOrNull(100) {
    linkDeferred.await()
}
link?.use { safeLink ->
    var counter = 0
    for (data in safeLink.incoming) {
        counter++
        if (counter >= 100) break
        println(data)
    }
}
// use函数会自动调用close,无需手动编写

注意:这种方式仅能处理接收方成功拿到对象的情况,仍需结合前面的方案覆盖接收方未获取到对象的场景。


内容的提问来源于stack exchange,提问作者progquester

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 19:37:24