您并没有对GC有太多的期望,但您假设了一些代码无法表达的内容。
你有一个
lazy val generate: BigData
在您的
LazilyEvaluatedBigData
课堂上,你有
val seq1: Seq[LazilyEvaluatedBigData]
在正在执行的代码中。
您的代码的行为符合预期,因为:
-
A.
lazy val
不是
def
:调用后,它保证将存储计算结果。如果您的程序内存不足,您不应该期望它会让其值被垃圾收集,并在需要时重新计算它。
-
A.
Seq
保证不会丢失任何元素。例如,a
List
不会因为程序内存不足而删除其任何元素。要做到这一点,您需要类似于带有软引用的序列的东西,或者您必须重写代码,以便在不再需要时不引用包含已处理元素的列表头。
如果将这两点结合起来考虑,那么代码在
flatMap
这个
seq1
是保存对多个
LazlyEvaluatedBigData
-实例,以及
懒惰的val
在那些里面
LazlyEvaluatedBigData
-实例都被计算并保存在内存中。
如果你想
BigData
当在
平面图
,只需声明
generate
像
def generate: BigData
然后你的
序号1
和
seq2
只能装下
String
s、 以及
平面图
将加载单个
BigData公司
比如,再把它挤压成一小块
Seq[Int]
使用
process
,然后
BigData公司
实例可能再次被垃圾收集。这将在没有太多内存的情况下成功运行:
// Very small wrapper class for Large BigData object
class LazilyEvaluatedBigData(a: String) {
def generate: BigData = new BigData(128)
}
// Contents of BigData are large
class BigData(m: Int) {
val data = Array.ofDim[Byte](1000000 * m)
def process: Seq[Int] = List(1,2,3)
}
val seq1: Seq[LazilyEvaluatedBigData] = List.fill(100)(new LazilyEvaluatedBigData(""))
val results1 = seq1.flatMap(_.generate.process)
println("run to end without OOM")
(如果
懒惰的val
)。
另一种选择是使用软参考(草图,未经彻底测试):
class LazilyEvaluatedBigData(a: String) {
import scala.ref.SoftReference
private def uncachedGenerate: BigData = new BigData(128)
private var cachedBigData: Option[SoftReference[BigData]] = None
def generate: BigData = {
val resOpt = for {
softRef <- cachedBigData
bd <- softRef.get
} yield bd
if (resOpt.isEmpty) {
val res = uncachedGenerate
cachedBigData = Some(new SoftReference(res))
res
} else {
resOpt.get
}
}
}
class BigData(m: Int) {
val data = Array.ofDim[Byte](1000000 * m)
def process: Seq[Int] = List(1,2,3)
}
val seq1: Seq[LazilyEvaluatedBigData] = List.fill(100)(new LazilyEvaluatedBigData(""))
val results1 = seq1.flatMap(_.generate.process)
println("run to end without OOM")
这也不会抛出OOM错误,希望它更接近
LazlyEvaluatedBigData
。
似乎无法替换
平面图
通过某种递归方法,确保
seq
因为
Seq公司
可以是任何东西,例如a
Vector
,如果不重建结构的其余部分,就很难分离头部。有人可能会尝试构建
平面图
如果更换了
Seq公司
通过
列表
,其中
head
可以更容易地进行gc。
编辑
如果而不是
Seq公司
你可以得到
列表
(这样可以对头部进行gc’d),那么这也可以:
class LazilyEvaluatedBigData(a: String) {
lazy val generate: BigData = new BigData(128)
}
class BigData(m: Int) {
val data = Array.ofDim[Byte](1000000 * m)
def process: Seq[Int] = List(1,2,3)
}
@annotation.tailrec
def gcFriendlyFlatMap[A](xs: List[LazilyEvaluatedBigData], revAcc: List[A], f: BigData => List[A]): List[A] = {
xs match {
case h :: t => gcFriendlyFlatMap(t, f(h.generate).reverse ::: revAcc, f)
case Nil => revAcc.reverse
}
}
val results1 = gcFriendlyFlatMap(List.fill(100)(new LazilyEvaluatedBigData("")), Nil, _.process.toList)
println("run to end without OOM")
println("results1 = " + results1)
然而,这似乎非常脆弱。上述示例之所以有效,是因为
gcFriendlyFlatMap
是尾部递归的。即使您添加了
看似无害的包装,比如
def nicerInterfaceFlatMap[A](xs: List[LazilyEvaluatedBigData])(f: BigData => List[A]): List[A] = {
gcFriendlyFlatMap(xs, Nil, f)
}
,一切都随着一声“OOM”而中断。我认为(还有
@tailrec
这是因为
xs
-列表保存在的堆栈框架上
nicerInterfaceFlatMap
,这样头部就不会被垃圾收集。
如果你不能改变
懒惰的val
在里面
LazlyEvaluatedBigData
,我建议您围绕它构建一个包装器,在那里您可以控制引用。