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
相关产品推荐
相关产品推荐

