You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The following code will throw an OutOfMemoryException (with heap size below 1GB):
classEngine {
varhandlers:List[(Int=>Unit)] =NildefregisterHandler(f: Int=>Unit) = handlers ::= f
defsetup() {
valbuffer=newArray[Byte](1000000)
for (s <-List("foo")) {
buffer(0) =1
registerHandler { n =>
callback(n)
}
}
}
defcallback(n: Int) = println(n)
}
objectTestextendsEngine {
defmain(args: Array[String]) {
println("start")
for (i <-0 until 1000) {
println("setup handler "+ i)
setup()
}
}
}
The OOM occurs because the buffer objects cannot be garbage collected. This is unexpected because only closures passed to registerHandler are retained, which have no free variables except (implicitly) the enclosing Engine instance. Certainly, they don't refer to any buffer.
However, closures are implemented as anonymous inner classes, and inner classes refer to their enclosing classes by chasing $outer pointers. Thus, the closures passed to registerHandler do not refer directly to the Engine instance but indirectly through the statically enclosing closure (the one passed to List("foo").map, which does refer to the buffer).
This behavior is particularly troublesome when using continuations, as the nesting of closures is no longer apparent from the program text:
valbuffer=newArray[Byte](1000000)
sleep(100) // cps version
buffer(0) =1
registerHandler { n => ... } // closure will hang on to buffer
A possible fix could be to treat this-references as proper free variables. More generally, anonymous classes could use separate outer_k pointers for the k-th enclosing object (called a 'display' in the literature). Of course only the used ones should be kept, so #1419 may be related.
The text was updated successfully, but these errors were encountered:
@sbocq said:
The leak may affect for-comprehensions as well and cause serialization overhead. The following snippet is extracted from a program where this is causing an issue.
caseclassId[A](a:A) {
defmap[B](f:A=>B):Id[B] =Id(f(a))
defflatMap[B](f: A=>Id[B]):Id[B] = f(a)
}
deffoo(x:Id[Int]):Id[Unit] =for {
u <-Id(4)
// v <- Id(5)
w <- x.map(_ + u)
} yield w
foo(Id(1))
The closure passed to map captures only 'u' if "v <- Id(5)" is commented out.
-> f Test$$anonfun$foo$1$$anonfun$apply$1 (id=21)
-> u$14
If the line is uncommented, although it is completely unrelated, the closure captures x.
Turing Eret (turingeret) said:
The behavior of unnecessarily hanging on to variables by closures is related to inner classes unnecessarily hanging on to variables, due to how closures are implemented as inner classes.
The following code will throw an OutOfMemoryException (with heap size below 1GB):
The OOM occurs because the buffer objects cannot be garbage collected. This is unexpected because only closures passed to registerHandler are retained, which have no free variables except (implicitly) the enclosing Engine instance. Certainly, they don't refer to any buffer.
However, closures are implemented as anonymous inner classes, and inner classes refer to their enclosing classes by chasing $outer pointers. Thus, the closures passed to registerHandler do not refer directly to the Engine instance but indirectly through the statically enclosing closure (the one passed to List("foo").map, which does refer to the buffer).
This behavior is particularly troublesome when using continuations, as the nesting of closures is no longer apparent from the program text:
A possible fix could be to treat this-references as proper free variables. More generally, anonymous classes could use separate outer_k pointers for the k-th enclosing object (called a 'display' in the literature). Of course only the used ones should be kept, so #1419 may be related.
The text was updated successfully, but these errors were encountered: