Skip to content

Closures hang on to free variables of all their parents, causing subtle memory leaks #5367

Closed
@scabug

Description

@scabug

The following code will throw an OutOfMemoryException (with heap size below 1GB):

class Engine {
  
  var handlers: List[(Int => Unit)] = Nil
  
  def registerHandler(f: Int => Unit) = handlers ::= f
  
  def setup() {
    
    val buffer = new Array[Byte](1000000)
    
    for (s <- List("foo")) {
      buffer(0) = 1

      registerHandler { n =>
        callback(n)
      }
    }
  }
  
  def callback(n: Int) = println(n)
}
  
object Test extends Engine {  
  def main(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:

val buffer = new Array[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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions