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

哪种JVM可解除引用对象清理实现方案最优?

JVM中依赖GC释放稀缺系统资源的规范实现方式?

假设对象K关联稀缺系统资源(如绑定到本地UDP的开放端口,单台机器仅65535个可用)。JVM应用需创建该对象、使用资源后,在系统触发GC时释放资源(显然,将其定义为AutoClosable并在withResource块中使用是更高效的方案,但不在本次讨论范围内)。

截至2023年,以Scala 2.13为例,JVM语言有多种实现方式:

import org.scalatest.funspec.AnyFunSpec

import java.lang.ref.Cleaner
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future}
import scala.ref.{PhantomReference, ReferenceQueue, WeakReference}

class GCCleaningSpike extends AnyFunSpec {

  import GCCleaningSpike._

  describe("System.gc() can dispose unreachable object") {

    it("with finalizer") {
      var v = Dummies._1()
      assertInc {
        v = null
      }
    }

    it("<: class with finalizer") {
      var v = Dummies._2()
      assertInc {
        v = null
      }
    }

    it("registered to a cleaner") {
      @volatile var v = Dummies._3()
      assertInc {
        v = null
      }
    }

    it("registered to a phantom reference cleanup thread") {
      @volatile var v = Dummies._4()
      assertInc {
        v = null
      }
    }

    it("registered to a weak reference cleanup thread") {
      @volatile var v = Dummies._4()
      assertInc {
        v = null
      }
    }
  }
}

object GCCleaningSpike {

  implicit lazy val ec: ExecutionContextExecutor = ExecutionContext.global

  case class WithFinalizer(fn: () => Unit) {

    case class _1() {
      override def finalize(): Unit = fn()
    }

    trait _2Base {
      override def finalize(): Unit = fn()
    }
    case class _2() extends _2Base

    final val _cleaner = Cleaner.create()
    case class _3() extends AutoCloseable {

      final private val cleanable = _cleaner.register(
        this,
        { () =>
          println("\ncleaned\n")
          fn()
        }
      )

      override def close(): Unit = cleanable.clean()
    }

    lazy val phantomQueue = new ReferenceQueue[_2Base]()
    case class _4() {
      val ref = new PhantomReference(this, phantomQueue)
    }

    val cleaningPhantom: Future[Unit] = Future {
      while (true) {
        val ref = phantomQueue.remove
        fn()
      }
    }

    lazy val weakQueue = new ReferenceQueue[_2Base]()
    case class _5() {
      val ref = new WeakReference(this, weakQueue)
    }

    val cleaningWeak: Future[Unit] = Future {
      while (true) {
        val ref = weakQueue.remove
        fn()
      }
    }
  }

  @transient var count = 0

  val doInc: () => Unit = () => count += 1

  def assertInc(fn: => Unit): Unit = {
    val c1 = count
    fn
    System.gc()
    Thread.sleep(1000)
    val c2 = count
    assert(c2 - c1 == 1)
  }

  object Dummies extends WithFinalizer(doInc)
}

各实现的问题点:

  • Finalizer方法(由class _1()演示):测试中有效,但始终不可靠(如JVM进程终止时可能不执行),且已在Java 11中废弃
  • 关联引用清理器的Cleanable成员(由class _3()演示):测试中无法通过解除引用和系统GC触发
  • 关联活跃ReferenceQueue监控线程的WeakReference/PhantomReference成员(分别由class _4()和_5()演示):同样无法通过解除引用和系统GC触发,且需要高成本的监控线程(大部分时间处于阻塞/休眠状态),暂不清楚是否可用低成本绿色线程或协程替代
  • 第三方实现:因选项过多未提供测试代码,典型示例为com.google.common.base.internal.Finalizer

我发现这些实现均不完美甚至无法正常工作,是否存在规范的、官方推荐且经过测试的实现方式?

更新1: 我完全认同不应依赖或滥用GC机制管理系统资源(如端口或堆外内存)。因此测试套件中的清理机制仅用于资源已绑定到特定作用域/生命周期的场景,且无论GC如何都会释放资源(如JVM终止时通过系统关闭钩子释放)。但仍存在资源在生命周期结束前被解除引用并GC的可能,借此可小幅优化内存占用。无需进一步说明,假设本次问题聚焦于这些特性/能力的有效使用场景,而非资源管理本身。

另外,原测试套件仅为简洁性使用Scala编写,后续会补充Java版本。


规范实现方案:Java Cleaner API

官方明确推荐的替代Finalizer的方案是Java 9引入的java.lang.ref.Cleaner,这是经过官方测试、性能和可靠性均优于Finalizer的实现。你测试中_3()的Cleaner未触发,大概率是测试代码存在问题,可从以下几点修正:

  1. 确保对象真正不可达
    测试中直接在当前方法内将变量置为null,JIT优化可能保留栈上的引用残留。建议将对象的创建和解除引用逻辑放到单独的方法中,确保对象脱离所有强引用:

    def createAndDiscard(): Unit = {
      val v = Dummies._3()
      // 使用对象
    }
    // 在assertInc中调用createAndDiscard(),而非直接在方法内操作变量
    
  2. 避免清理逻辑持有强引用
    清理逻辑不能持有被清理对象的强引用,否则对象永远无法被GC回收。调整后的_3()实现:

    final val _cleaner = Cleaner.create()
    case class _3() extends AutoCloseable {
      // 用独立Runnable持有清理逻辑,避免对当前对象的强引用
      private val cleanable = _cleaner.register(this, new Runnable {
        override def run(): Unit = {
          println("\ncleaned by cleaner\n")
          fn()
        }
      })
    
      override def close(): Unit = cleanable.clean()
    }
    
  3. 处理GC的不确定性
    System.gc()仅为GC执行建议,不保证立即触发。测试时可配合System.runFinalization(),或添加JVM参数-XX:+ExplicitGCInvokesConcurrent强制触发GC。

PhantomReference的正确使用方式

之前的_4()实现存在自引用问题:对象内部持有自己的PhantomReference,导致对象永远无法被判定为不可达。正确做法是外部持有引用,而非对象自身持有:

lazy val phantomQueue = new ReferenceQueue[AnyRef]()
object PhantomCleaner {
  val cleaningTask: Future[Unit] = Future {
    while (true) {
      val ref = phantomQueue.remove().asInstanceOf[PhantomReference[_3]]
      // 执行清理逻辑
      fn()
    }
  }

  def register(obj: AnyRef): Unit = {
    new PhantomReference(obj, phantomQueue)
  }
}
case class _4() {
  // 外部注册引用,对象自身不持有
  PhantomCleaner.register(this)
}

总结

  • 官方推荐的规范实现是**java.lang.ref.Cleaner**,它解决了Finalizer的可靠性和性能问题,是当前JVM环境下的最优选择
  • 无论使用Cleaner还是引用队列,都必须确保对象能真正成为不可达状态,且清理逻辑不持有对象的强引用
  • 始终配合AutoCloseable实现,让用户可以主动释放资源,GC清理仅作为兜底机制

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 08:52:01