55

After having read this article about Memory Leaks, I am wondering whether using lambdas in Kotlin Android project is safe. It's true that lambda syntax makes me program with more ease, but what about the Memory Leaks ?

As an example of the problematic, I've taken a piece of code from one of my projects, where I build 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 the two lambdas set for positive and negative buttons lead to Memory Leaks ? (I also mean, are kotlin lambdas simply converted to Java Anonymous functions ?)

Edit : Maybe I've got my answer in this Jetbrains Topic.

loloof64
  • 5,252
  • 12
  • 41
  • 78
  • 3
    So does lambdas capture enclosing object when it doesn't use any methord or field of enclosing object? They are different points of: https://discuss.kotlinlang.org/t/function-literals-and-reference-to-enclosing-class/416 https://discuss.kotlinlang.org/t/lambda-expression-or-anonymous-function-keep-an-implicit-reference-of-the-enclosing-class/1429 – Andrew Sep 01 '17 at 03:34
  • Thank you very much. Gonna listen to them soon :) – loloof64 Sep 01 '17 at 05:44
  • In fact no so useful fact. Anyway thank you – loloof64 Sep 02 '17 at 05:46
  • imo, this linked discussion does not tell you if you're safe (or not) from leaking memory but it rather addresses an optimisation issue during compilation. – stdout Apr 17 '23 at 07:26

3 Answers3

43

Edit (February 19, 2017): I received a very comprehensive reply from Mike Hearn regarding this issue:

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.


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


are kotlin lambdas simply converted to Java Anonymous functions ?

I was asking this question myself (one simple correction here: these are called Anonymous Classes, not functions). There is no clear answer in the Koltin documentation. They just state that

Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure, i.e. those variables that are accessed in the body of the function.

It is a bit confusing what they mean by variables that are accessed in the body of the function. Is the reference to the instance of the enclosing class also counted?

I've seen the topic you are referencing in your question but it seems it is outdated as for now. I've found more up-to-date information here:

Lambda expression or anonymous function keep an implicit reference of the enclosing class

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

Why Anonymous Inner Classes are bad?

From the Java specs:

An instance i of a direct inner class C of a class O is associated with an instance of O, known as the immediately enclosing instance of i. The immediately enclosing instance of an object, if any, is determined when the object is created

What this means is that the anonymous class will always have an implicit reference to the instance of the enclosing class. And since the reference is implicit there is no way to get rid of it.

Look at the 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 the memory leak until the long-running task is executed. One workaround for this is to use static nested class.

Since Kotlin's non-inlined lambdas hold the reference to the instance of the enclosing class they have similar issues regarding memory leaks.

Bonus: Quick Comparison With Other Lambda Implementations

Java 8 Lambdas

Syntax:

  • Declare SAM (single abstract method) interface

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

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

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

Memory leak issues: As stated in specs:

References to this -- including implicit references through unqualified field references or method invocations -- are, essentially, references to a final local variable. Lambda bodies that contain such references capture the appropriate instance of this. In other cases, no reference to this is retained by the object.

Simply put, if you do not use any fields / methods from the enclosing class there is no implicit reference to this as in the case of anonymous classes.

Retrolambda

From the docs

Lambda expressions are backported by converting them to anonymous inner classes. This includes the optimization of using a singleton instance for stateless lambda expressions to avoid repeated object allocation.

I guess, it's self-explanatory.

Apple's Swift

Syntax:

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

    func someFunctionThatTakesAClosure(closure: (String) -> Void) {}
    
  • Pass the closure

    someFunctionThatTakesAClosure { print($0) }
    

    Here, $0 refer to the closure’s first String argument. This corresponds to it in Kotlin. Note: Unlike Kotlin, in Swift we can refer also to the other arguments like $1, $2 etc.

Memory leak issues:

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

Also developers can easily specify that they want to capture only the weak reference:

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

I wish it were possible in Kotlin too :)

Stan Mots
  • 1,193
  • 2
  • 15
  • 16
  • 3
    Indeed I meant Anonymous classes. Yes, Kotlin shoud really have implemented the weak reference capture option for lambda, really agree with that. So I see the point, I need to be as carefull with Kotlin lambda than with Java inner/anonymous classes. So kotlin lambdas are not safe, I should use static inner class and with WeakReferences to any View reference inside them. – loloof64 Feb 16 '17 at 13:50
  • WeakReferences has its share of problems, and less obvious than memory leaks. Also my guess is that could reimplement what you want in Kotlin so that your lambdas would use weakReference (with a little help of `crossinline`) – voddan Feb 18 '17 at 11:39
  • Of course, weak references sometimes can cause problems too. But, in my opinion, it's always better to have a choice. Regarding `crossinline`, correct me if I'm wrong, but if we are using third-party library it is impossible to enforce inline behavior from the calling side, right? – Stan Mots Feb 18 '17 at 11:59
  • 1
    That is hard to understand. So when the lambda captures, it creates new objects and they presumably contain references to the variables used in the lambda. Do these objects also contain a reference to `this` of the outer class, if `this` is never used in the lambda, or only if `this` is used? – BeniBela Dec 16 '18 at 23:52
  • @BeniBela The lambda will capture a reference to `this` only if you are using properties and/or calling methods of `this` object inside lambda's body. – Stan Mots Dec 17 '18 at 21:28
  • @StanMots As far as I can see, it has this reference regardless you use a variable or method of the enclosing class. If you use it though, you just have a new instance instead a singleton one. – stdout Mar 16 '19 at 10:46
  • Also, I think closures are only created in certain situations like if you return a function type or if you pass it into another function. When it's only a parameter, there should be nothing to worry about in terms of leaking things. inlining "boils away" the problem of object creation (thus the runtime overhead) - nothing much to do with leaking. I would be glad if someone approves this though :). – stdout Mar 16 '19 at 11:07
  • 1
    Maybe I'm just confused because I'm used to Swift's behavior, but I don't feel like Mike Hearn's response fully answers the question. In Swift, self is always captured unless it is declared as [weak self]. In Java (from your link), "lambdas that do not capture members from the enclosing instance do not hold a reference to it". Mike Hearn implies that Kotlin's behavior is similar to Java, but it isn't completely clear. "If the lambda doesn't capture" is still somewhat ambiguous. What determines if the lambda will capture? – Greg Brown Apr 05 '19 at 10:18
  • 1
    @StanMots You mention that "the lambda will capture a reference to this only if you are using properties and/or calling methods of this object inside lambda's body". Can you share anything that specifically documents this behavior? – Greg Brown Apr 05 '19 at 10:22
16

Memory leaks happen when some object which should be removed because it isn't needed anymore can not be removed because something which has a longer lifetime has a reference to this object. The simplest example is storing the reference to the Activity in the static variable (I'm talking from the Java perspective, but it's similar in Kotlin): after the user has clicked on 'Back' button the Activity is not needed anymore, but it will be kept in memory nevertheless - because some static variable still points to this activity.
Now, in your example you are not assigning your Activity to some static variable, there're no Kotlin's objects involved which could keep your Activity from being garbage-collected - all the objects involved in your code have roughly the same lifetime, which means there will be no memory leaks.

P.S. I've refreshed my memories on the Kotlin's implementation of the lambdas: in the case of the negative button click handler you aren't referencing the outer scope, thus compiler will create single instance of the click listener which will be reused across all the clicks on this button. In the case of the positive button click listener, you're referencing the outer scope (this@MainActivity), so in this case Kotlin will be creating a new instance of the anonymous class each time you create a dialog (and this instance will have the reference to the outer class, MainActivity), so the behavior is exactly the same as if you had written this code in Java.

aga
  • 27,954
  • 13
  • 86
  • 121
  • Thank you. You confirm the impression I got when "profiling" the application : I did not notice any memory problem. Just the fact that the GC is called from time to time. – loloof64 Feb 16 '17 at 11:23
  • But, as another confusion point for me, let's say that in the positive button callback I can add (without compilation error) the following line `explorerRecyclerView.invalidate()`, where explorerRecyclerView is a member of my MainActivity class, indirectly host of the lambda. So, the activity Garbage Collection could be prevented, right ? – loloof64 Feb 16 '17 at 11:28
  • 2
    Well, it's basically the same as in Java: will the activity be garbage-collected when the click listener of the Dialog is doing some work (assuming this click listener uses some of the activity methods)? No. Will it be garbage-collected once the click listener has performed its duties and the activity is finished? Yes. – aga Feb 16 '17 at 11:38
  • @aga Yes Because of single-threaded(Main thread) architecture of android view system. – David May 26 '20 at 09:56
16

Here's a simple example of a (potential) leak, where the closure/block captures this:

class SomeClass {
    fun doWork() {
        doWorkAsync { onResult() } // leaks, this.onResult() captures `this`
    }

    fun onResult() { /* some work */ }
}

You'll need to use a WeakReference.

fun doWork() {
    val weakThis = WeakReference(this)
    doWorkAsync { weakThis?.get()?.onResult() } // no capture, no leak!
}
Graham Perks
  • 23,007
  • 8
  • 61
  • 83