JVM:如何管理JNI创建的堆外内存

时间:2018-06-05 13:44:13

标签: java scala jvm swig torch

我在Torch库周围构建了一个Scala包装器。我使用Swig来构建胶水层。它允许我在堆外创建张量,我只能通过显式调用库的静态方法来释放它。但是,我想以命令的方式使用张量,而不必担心释放内存,就像Java中的任何普通对象一样。

我能想到这样做的唯一方法是(错误地)以下列方式使用JVM的垃圾收集器:

A'内存管理员'跟踪消耗的堆外内存量,当达到阈值时,它会调用System.gc()

object MemoryManager {    
  val Threshold: Long = 2L * 1024L * 1024L * 1024L // 2 GB
  val FloatSize = 4
  private val hiMemMark = new AtomicLong(0)

  def dec(size: Long): Long = hiMemMark.addAndGet(-size * FloatSize)
  def inc(size: Long): Long = hiMemMark.addAndGet(size * FloatSize)

  def memCheck(size: Long): Unit = {
    val level = inc(size)
    if (level > Threshold) {
      System.gc()
    }
  }
}

张量本身包含在一个类中,使用finalize方法释放堆外内存,如下所示:

class Tensor private (val payload: SWIGTYPE_p_THFloatTensor) {
  def numel: Int = TH.THFloatTensor_numel(payload)

  override def finalize(): Unit = {
    val memSize = MemoryManager.dec(numel)
    TH.THFloatTensor_free(payload)
  }    
}

张量创建由工厂方法完成,该方法通知内存管理器。例如,要创建零的Tensor:

object Tensor {
  def zeros(shape: List[Int]): Tensor = {
      MemoryManager.memCheck(shape.product)
      val storage = ... // boilerplate
      val t = TH.THFloatTensor_new
      TH.THFloatTensor_zeros(t, storage)
      new Tensor(t)
  }
}

我意识到这是一种天真的做法,但我可以逃脱这个吗?它似乎工作正常,也是在并行运行时(它会产生大量多余的System.gc()调用,但除此之外什么都没有) 或者你能想到一个更好的解决方案吗?

谢谢。

1 个答案:

答案 0 :(得分:2)

有一个更确定的选项 - 明确管理的内存区域

所以,大致如果我们有这样一个类:

class Region private () {
  private val registered = ArrayBuffer.empty[() => Unit]
  def register(finalizer: () => Unit): Unit = registered += finalizer
  def releaseAll(): Unit = {
    registered.foreach(f => f()) // todo - will leak if f() throws
  }
}

我们可以有一个实现所谓“贷款模式”的方法,它为我们提供了一个新的区域,然后处理了重新分配

object Region {
  def run[A](f: Region => A): A = {
    val r = new Region
    try f(r) finally r.releaseAll()
  }
}

然后需要手动释放的东西可以被描述为采用隐式Region

class Leakable(i: Int)(implicit r: Region) {
  // Class body is constructor body, so you can register finalizers
  r.register(() => println(s"Deallocated foo $i"))

  def foo() = println(s"Foo: $i")
}

您可以以相当无样板的方式使用它:

Region.run { implicit r =>
  val a = new Leakable(1)
  val b = new Leakable(2)
  b.foo()
  a.foo()
}

此代码生成以下输出:

Foo: 2
Foo: 1
Deallocated foo 1
Deallocated foo 2

这种方法有一点限制(如果你尝试将Leakable分配给run中传递的闭包之外的变量,它的范围将不会被提升),但会更快并且保证即使禁用对System.gc的来电也能正常工作。