scala 哪个JVM对象清除实现最好用?

ohtdti5x  于 6个月前  发布在  Scala
关注(0)|答案(1)|浏览(69)

假设对象K与稀缺的系统资源相关联(例如,绑定到本地主机UDP上的开放端口,其中每台机器只有65535个可用)。JVM应用程序需要创建对象,使用资源执行任务,并在请求系统GC时释放它。(显然,将其定义为AutoClosable并在withResource块中使用它可能是一个更有效的选择,但这不是这个问题的主题)
根据2023,任何JVM语言都有几种实现(以Scala 2.13为例):

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中被弃用
  • 与引用清理器关联的可清理成员(如class _3()所示),在上述测试中,它们不能通过解引用和系统GC触发
  • 与活动ReferenceQueue监视线程关联的WeakReference/RectomReference成员(分别由class _4()_5()表示)。同样,它们也不能通过解引用和系统GC触发,此外,它们都需要在其生命周期的大部分时间内处于阻塞/休眠状态的昂贵监视线程。如果该线程可以用低成本的绿色线程或协程替换,则不清除。
  • 第三方实现,由于选项太多,没有给出测试代码。

我无法感知这些实现是完美的,甚至是功能。有没有一个规范的,官方推荐和测试的方法来做到这一点?

**更新1:**我完全同意在管理系统资源(例如端口或堆外内存)时不应依赖和滥用GC机制。这就是为什么测试套件中显示的清理机制仅在资源已经绑定到范围/生命周期的实际情况下使用。并且无论GC如何都会被释放(例如,在JVM终止的情况下,它们将通过系统关闭钩子释放)。尽管如此,仍有可能在资源的生命周期之前很久就对资源进行解引用和GC,并且可以实现内存占用的轻微改善。在没有进一步澄清的情况下,让我们假设问题是关于这些特性/功能的有效用例,而不是资源管理

另外,最初的测试套件只是为了简短而用Scala编写的,稍后我将添加Java版本。

btqmn9zl

btqmn9zl1#

假设您有这样做的合理理由,那么Cleaner是自Java 9以来推荐的方法,正如Object.finalize()上的Javadoc所指出的那样

已弃用,终结机制本身存在问题,终结会导致性能问题、死锁、挂起,终结器出错会导致资源泄露,如果不再需要终结,则无法取消终结;并且在对不同对象的finalize方法的调用之间没有指定顺序。此外,没有关于终结时间的保证。finalize方法可能只在无限延迟之后才在可终结对象上被调用,如果有的话。其示例持有非堆资源的类应该提供一个方法来启用这些资源的显式释放,如果合适的话,它们还应该实现AutoCloseablejava.lang.ref.Cleanerjava.lang.ref.PhantomReference提供了更灵活、更有效的方法来在对象变得不可访问时释放资源

例如,JDK的内部NativeBuffer将其用作释放本机内存的最后手段。
我不是Scala的人,所以不能评论你的Scala代码中发生了什么,这里有一个Java的例子,注意System.gc()是对JVM的提示,它不保证GC会被调用触发,因此使用while循环和一些字节数组分配来增加内存压力。

import java.lang.ref.Cleaner;

class CleanerExample {

    private static final Cleaner cleaner = Cleaner.create();

    private static volatile boolean cleaned;

    CleanerExample() {
        cleaner.register(this, () -> cleaned = true);
    }

    public static void main(String[] args) throws Exception {
        new CleanerExample();
        while (!cleaned) {
            var waste = new byte[512 * 1024 * 1024];
            System.gc();
            Thread.sleep(1000);
            System.out.println("Waiting to be cleaned...");
        }
        System.out.println("Cleaned");
    }
}

字符串

相关问题