以下定义导致内存泄漏:
def enumIterator1[E, F[_]: Monad](x: => Iterator[E]) : EnumeratorT[E, F] =
new EnumeratorT[E, F] {
def apply[A] = (s: StepT[E, F, A]) => {
def go(xs: Iterator[E]): IterateeT[E, F, A] =
if(xs.isEmpty) s.pointI
else {
val next = xs.next
s mapCont { k =>
k(Iteratee.elInput(next)) >>== enumIterator1[E, F](xs).apply[A]
}
}
go(x)
}
}
通过以下测试可以观察到泄漏:
(Iteratee.fold[Array[Byte], IO, Long](0L)(_+_.length)
&= enumIterator1(
Iterator.continually(
Array.fill(1 << 16)(0.toByte)).take(1 << 16))
).run.unsafePerformIO
然而,一个小的改变(即移动xs.next
调用)会阻止泄漏:
def enumIterator1[E, F[_]: Monad](x: => Iterator[E]) : EnumeratorT[E, F] =
new EnumeratorT[E, F] {
def apply[A] = (s: StepT[E, F, A]) => {
def go(xs: Iterator[E]): IterateeT[E, F, A] =
if(xs.isEmpty) s.pointI
else {
// val next = xs.next (moved down)
s mapCont { k =>
val next = xs.next
k(Iteratee.elInput(next)) >>== enumIterator1[E, F](xs).apply[A]
}
}
go(x)
}
}
为什么吗
我有一个模糊的概念,即解释与闭包的参考模式有关,但我不能想出这种行为的具体原因。我正在尝试追踪a different memory leak,我怀疑(希望?)理解此泄漏可能有助于确定该漏洞的原因。
答案 0 :(得分:3)
问题是传递给mapCont
的匿名函数会在next
之后关闭。反过来,这是由我们传递给enumIterator的惰性变量关闭的,enumIterator由Enumerator
形成的新enumIterator1
关闭,apply
由mapCont
中的匿名函数关闭。 1}},最后由传递给next
的匿名函数关闭以进行下一次迭代。
因此,通过一系列闭包,每个调查员都会关闭其前任。无论是否捕获next
,都可能发生这种情况,因此您无论如何都会有轻微的内存泄漏。但是,您最终会在其中一个闭包中捕获next
,这意味着迭代器生成的每个值都会保留在内存中,直到整个过程完成(并且这些值会占用大量内存)。
通过移动mapCont
传递给next
的匿名函数,def enumIterator1[E, F[_]: Monad](x: => Iterator[E]) : EnumeratorT[E, F] =
new EnumeratorT[E, F] {
def apply[A] = {
val xs = x
def innerApply(s: StepT[E, F, A]): IterateeT[E, F, A] = {
if(xs.isEmpty) s.pointI
else {
val next = xs.next
s mapCont { cont => // renamed k to cont, as the function, rather than the variable, is k
cont(Iteratee.elInput(next)) >>== innerApply
}
}
}
innerApply
}
}
不再被我们的闭包链捕获,因此主内存泄漏消失(尽管你的闭包仍然关闭)彼此之间,这可能是一个问题)。
解决此问题的最佳方法可能是简化它。正如Brian Kernighan所说:
每个人都知道调试的难度是首先编写程序的两倍。所以,如果你在写作时就像你一样聪明,你将如何调试呢?
我不确定我是否完全理解代码,但我怀疑以下内容是等效的:
EnumeratorT
您也可以从更明确的事情中受益。例如,如果不是让匿名-XX:+HeapDumpOnOutOfMemoryError
隐式关闭其范围内所需的任何内容,那么您可以定义一个具有顶级范围的命名类,并传递它所需的任何内容。
我使用javap
,VisualVM和enumIterator1[E, F](xs).apply[A]
来查找问题的原因。它们应该是你需要的一切。
我想我开始弄清楚代码应该做什么,并且我已经相应地更新了我的代码。我认为问题在于使用EnumeratorT
。代码创建了一个新的xs
只是为了获得它的apply方法,但创建了一个名字变量并在这个过程中关闭了所有和它的狗。由于innerApply
的值不会从一次递归更改为下一次递归,因此我们创建一个xs
方法,该方法关闭val innerApply
,然后重新使用def enumIterator[E, F[_]](x: => Iterator[E])(implicit MO: MonadPartialOrder[F, IO]) : EnumeratorT[E, F] =
new EnumeratorT[E, F] {
import MO._ // Remove this line, and you can copy and paste it into your code
def apply[A] = {
def go(xs: Iterator[E])(s: StepT[E, F, A]): IterateeT[E, F, A] =
if(xs.isEmpty) s.pointI
else {
s mapCont { k =>
val next = xs.next
k(elInput(next)) >>== go(xs)
}
}
go(x)
}
}
。
我很好奇,所以我在Scalaz源代码中查看它们是如何解决这个问题的。这里有一些与Scalaz本身类似的代码:
xs
他们使用currying而不是闭合来捕获{{1}},但它仍然是一种“内在应用”方法。