Lambdas and implicit references to the instance of the enclosing class

Hi,

I have a simple question. Unfortunately, I didn’t find a clear answer in the documentation.

Please can you clarify is an implicit reference to the instance of the enclosing class always created when we use lambda expressions? For example, in Java there are two different situation:

  • Anonymous inner classes always create implicit reference

  • Java 8 lambdas create implicit reference only when we are using some method or field from the enclosing class

The clarification of Kotlin’s lambdas behavior is really important to prevent possible memory leaks (e.g., when passing lambdas to rx operators).

1 Like

The “Show Kotlin Bytecode” view is very helpful for answering such questions, if you can read bytecode at least (it’s not that hard). You can open it in the tools menu.

Like in Java, what happens in Kotlin varies in different cases.

  • If the lambda is passed to an inline function and isn’t marked noinline, then the whole thing boils away and no additional classes or objects are created.
  • If the lambda doesn’t capture, then it’ll be emitted as a singleton class whose instance is reused again and again (one class+one object allocation).
  • If the lambda captures then a new object is created each time the lambda is used.

Thus it is similar behaviour to Java except for the inlining case where it’s even cheaper. This efficient approach to encoding lambdas is one reason why functional programming in Kotlin is more attractive than in Java.

Note, however, the following caveat. Currently the Kotlin compiler does not use the Java 8 invokedynamic infrastructure for spinning lambda classes at runtime. Thus whilst Kotlin lambdas are comparable in terms of runtime performance to Java, they take up more space on disk and require more class files in the shipped JARs which in turn imposes a small load time penalty, as it’s faster to create code entirely in memory on-demand than load and decompress from a ZIP file. The Kotlin team plan to start emitting Java 8 style bytecode in future.

Switching to Java 8’s LambdaMetaFactories via invokedynamic unlocks other benefits: it takes less RAM and creates less lock contention inside the JVM itself. A future version of HotSpot is planned to continue to improve the efficiency of lambdas:

http://openjdk.java.net/jeps/8158765

3 Likes

Thank you for such a comprehensive response! This is just what I was looking for.

I agree, the “Show Kotlin Bytecode” feature could be helpful in this case. But, I think, this information should be added to the documentation anyway. You know, people can be lazy sometimes :slight_smile:

Does the observation about Kotlin compiler not using JVM’s invokedynamic for lambdas also apply to the Dalvik equivalents invoke-custom and invoke-polymorphic?

I’m still a bit confused by this. In Swift, self is always captured unless it is declared as [weak self]. In Java (State of the Lambda), “lambdas that do not capture members from the enclosing instance do not hold a reference to it”.

“If the lambda doesn’t capture” is somewhat ambiguous. What determines if the lambda will capture? Does it always capture this (like Swift), or does it only capture this if an instance member is referenced (like Java)?

I would be very surprised if the behaviour is not as in Java. One reason is compatibility, the other reason is that from a program perspective it is almost invisible if there is an unused capture (there is the issue of memory leaks/the capture preventing garbage collection of the container).
In this context (looking at how the JVM works) there are two ways to reference an implicit receiver (this or self object - but in Kotlin there can be multiple such objects). The two ways are either to access (read/write) an instance field, or by invoking an instance method on it. Both require a reference to the object and therefore cause a reference to that object to be “captured” so that the call is valid.

As a way of thinking about it, the behaviour should be identical to the case where you put the this reference in a variable and refer to the object through this variable.

Thanks for the response. It makes sense, and I agree. I’d just like to see some “official” documentation to verify this.

Is there a schedule to switch lambda to invokedynamic? Kotlin 1.3.50 still does not use invokedynamic for lambdas.

1 Like