Kotlin: safe lambda (no memory leak)?

After reading this article about memory leaks , I wonder if using lambdas in an Android Kotlin project is safe. It is true that the lambda syntax makes me a program with greater ease, but what about memory leaks?

As an example of a problem, I took a snippet of code from one of my projects where I create an AlertDialog. This code is inside the MainActivity class of my project.

fun deleteItemOnConfirmation(id: Long) : Unit { val item = explorerAdapter.getItemAt(id.toInt()) val stringId = if (item.isDirectory) R.string.about_to_delete_folder else R.string.about_to_delete_file val dialog = AlertDialog.Builder(this). setMessage(String.format(getString(stringId), item.name)).setPositiveButton( R.string.ok, {dialog: DialogInterface, id: Int -> val success = if (item.isDirectory) ExplorerFileManager.deleteFolderRecursively(item.name) else ExplorerFileManager.deleteFile(item.name) if (success) { explorerAdapter.deleteItem(item) explorerRecyclerView.invalidate() } else Toast.makeText( this@MainActivity , R.string.file_deletion_error, Toast.LENGTH_SHORT).show() }).setNegativeButton( R.string.cancel, {dialog: DialogInterface, id: Int -> dialog.cancel() }) dialog.show() } 

My question is very simple: can two lambda sets for positive and negative buttons lead to memory leaks? (I also mean that kotlin lambdas just converts to Java Anonymous functions?)

Edit: Perhaps I have an answer in this Jetbrains thread .

+7
android lambda memory-leaks kotlin
source share
2 answers

Edit (February 19, 2017): I received a very detailed answer from Mike Hearn on this issue.

As in Java, what happens in Kotlin differs in different cases.

  • If the lambda is passed to the built-in function and noinline is marked, then all this will boil, and no additional classes or objects are created.
  • If the lambda is not fixed, then it will be selected as a singleton class, an instance of which is used again and again (one class + one distribution object).
  • If lambda captures, then a new object is created every time a lambda is used.

So this is the same behavior with Java, except in the case of addition where it is even cheaper. This efficient approach to lambda coding is one of the reasons why functional programming in Kotlin is more attractive than in Java.


Edit (February 17, 2017): I posted a question on this topic in Kotlin discussions . Perhaps Kotlin engineers will bring something new to the table.


are kotlin lambdas just converted to Java Anonymous functions?

I asked this question myself (one simple correction here: they are called anonymous classes, not functions). There is no clear answer in the Koltin documentation. They are just state , which

Using functions of a higher order imposes certain penalties at runtime: each function is an object, and it captures a closure, that is, these variables that are available in the body of the function.

This is a bit confusing what they mean by variables that are available in the body of the function. Is reference to instance of inclusion class taken into account?

I saw a topic that you refer to in your question, but it seems to be out of date. I found more relevant information here :

Lambda expression or anonymous function retain implicit references spanning class

So, unfortunately, it seems that Kotlin lambdas have the same problems as Java Anonymous Inner Classes.

Why are anonymous inner classes bad?

From Java specs :

The instance i of the direct inner class C of class O is associated with the instance of O, known as the immediate instance of i. The attached instance of the object, if any, is determined when the object is created

This means that an anonymous class will always have an implicit reference to an instance of the incoming class. And since the link is implicit, there is no way to get rid of it.

Look at a trivial example

 public class YourActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new Thread(new Runnable() { // the inner class will keep the implicit reference to the outer activity @Override public void run() { // long-running task } }).start(); } } 

As you can see, in this case there will be a memory leak until a lengthy task is completed. A workaround for this is to use a static nested class.

Since Kotlin's non-inlined lambdas contain a reference to an instance of the surrounding class, they have similar problems with memory leaks.

Bonus: quick comparison with other lambda implementations

Java 8 Lambdas

Syntax:

  • Declare a SAM interface (single abstract method)

     interface Runnable { void run(); } 
  • Use this interface as type for lambda

     public void canTakeLambda(Runnable r) { ... } 
  • Pass your lambda

     canTakeLambda(() -> System.out.println("Do work in lambda...")); 

Memory leak problems: As indicated in the specification :

References to this — including implicit references through unqualified field references or method calls — are essentially references to the final local variable. Lambda bodies that contain such references capture the corresponding instance of this. In other cases, a reference to this is not stored by the object.

Simply put, if you are not using any fields / methods from the enclosing class, there is no implicit reference to this , as is the case with anonymous classes.

Retrolambda

From docs

Lambda expressions are passed back by converting them to anonymous inner classes. This includes optimizing the use of a singleton instance for lambda expressions without apathy to avoid re-distribution.

I think this is self-evident.

Apple swift

Syntax:

  • The declaration is similar to Kotlin, in Swift lambdas are called closures:

     func someFunctionThatTakesAClosure(closure: (String) -> Void) {} 
  • Skip closing

     someFunctionThatTakesAClosure { print($0) } 

    Here $0 refers to the first argument of the String closure. This corresponds to it in Kotlin. Note. Unlike Kotlin, in Swift we can also refer to other arguments, such as $1 , $2 , etc.

Memory leak problems:

In Swift, as in Java 8, a closure captures a strong reference to self ( this in Java and Kotlin) only if it accesses an instance property, such as self.someProperty , or if the closure calls a method on the instance, such as self.someMethod() .

Also, developers can easily indicate that they want to capture only a weak link:

  someFunctionThatTakesAClosure { [weak self] in print($0) } 

I would like it to be possible in Kotlin :)

+5
source share

Memory leaks occur when an object that needs to be deleted because it is no longer needed cannot be deleted because something that has a longer lifetime has a link to this object. The simplest example is storing the reference to the Activity in the static variable (I say from the point of view of Java, but this is similar to Kotlin): after the user clicks the back button, the Activity not required anymore, but it will be saved in memory, however less because some kind of static variable still points to this activity.
Now in your example, you are not assigning your Activity some static variable, Kotlin object not involved, which could save your Activity from garbage collection - all objects involved in your code have approximately the same lifetime, which means that there will be no memory leaks.

PS I updated my memories of the Kotlin lambda implementation: in the case of a negative button click handler, you are not referring to an external area, so the compiler will create a separate instance of the click listener that will be reused in all clicks on this button. In the case of a positive button click listener, you are referring to an external area ( this@MainActivity ), so in this case Kotlin will create a new instance of the anonymous class every time you create a dialog (and this instance will have a link to the external class, MainActivity ), therefore, the behavior is exactly the same as if you wrote this code in Java.

+6
source share

All Articles