JVM:如何管理 JNI 创建的堆外内存
JVM: How to manage off-heap memory created by JNI
我正在围绕 Torch libraries. I'm using Swig 构建一个 Scala 包装器来构建粘合层。它允许我在堆外创建张量,我只能通过显式调用库的静态方法来释放它。但是,我想以命令的方式使用张量,而不必担心释放内存,就像 Java 中的任何普通对象一样。
我能想到的唯一方法是按以下方式(误)使用 JVM 的垃圾收集器:
A 'memory manager' 跟踪消耗的堆外内存量,当达到阈值时,它调用 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()
}
}
}
张量本身被包装在一个 class 中,使用 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)
}
}
张量的创建是通过通知内存管理器的工厂方法完成的。例如,要创建零张量:
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()
的多余调用,但除此之外什么都没有)
或者你能想到更好的解决方案吗?
谢谢。
有一个更具确定性的选项 - 显式管理的内存区域
所以,如果我们有一个像这样的 class:
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
}
}
我们可以有一个方法实现所谓的 "Loan pattern" 给我们一个新的区域然后处理释放
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
被禁用也能正常工作。
我正在围绕 Torch libraries. I'm using Swig 构建一个 Scala 包装器来构建粘合层。它允许我在堆外创建张量,我只能通过显式调用库的静态方法来释放它。但是,我想以命令的方式使用张量,而不必担心释放内存,就像 Java 中的任何普通对象一样。
我能想到的唯一方法是按以下方式(误)使用 JVM 的垃圾收集器:
A 'memory manager' 跟踪消耗的堆外内存量,当达到阈值时,它调用 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()
}
}
}
张量本身被包装在一个 class 中,使用 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)
}
}
张量的创建是通过通知内存管理器的工厂方法完成的。例如,要创建零张量:
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()
的多余调用,但除此之外什么都没有)
或者你能想到更好的解决方案吗?
谢谢。
有一个更具确定性的选项 - 显式管理的内存区域
所以,如果我们有一个像这样的 class:
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
}
}
我们可以有一个方法实现所谓的 "Loan pattern" 给我们一个新的区域然后处理释放
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
被禁用也能正常工作。