How Kotlin helps you avoid memory leaks

I then wrote the following test using leakcanary and my own @LeakTest annotation to run their memory analyzer only in this test.

The test performs a button click and because that’s the only thing we are doing, the activity will get destroyed immediately after and creating a leak since we didn’t wait for 20 seconds.

If we try to execute the testLeaks test inside MyKLeakTest you will see that the tests passes meaning that we didn’t detect any memory leaks.

This result confused me a lot, at the end of the day I was replacing that anonymous Java class with an Anonymous inner class and since it was an instance of a functional Java interface I could use a lambda expression instead (more about single abstract methods or SAM conversions here).

I was so confused and felt so stupid that I tweeted this:And got this reply which made me laugh.

I wish my skills were up to that level :DIt is so easy to get trapped in this circle of “there’s nothing wrong but I can’t find what’s going on” that I went back to basics.

I wrote a new activity, same code but this time I kept it in Java.

I changed the test to point to this new Activity, run it and this time… it failed.

Things started to make a little more sense now.

The Kotlin code had to be different to the Java code, something had happened there and there was just one place to look for it.

The bytecode.

Analyzing LeakActivity.

javaTo begin with I analyzed the Dalvik Bytecode of the Java activity.

To do that you have to analyze your apk via Build/Analyze APK.

and then select from the classes.

dex file the class you want to analyze.

We right click on the class and select Show Bytecode to get the Dalvik Bytecode of the class.

I’ll just focus on the startAsyncWork method since we know it’s the place where the memory leak happens.

We know that an anonymous class keeps a reference to the outer class so we are going to be looking for that.

In the bytecode above we create a new instance of LeakActivity$2 and we store it in v0 (line 10).

new-instance v0, Lcom/marcosholgado/performancetest/LeakActivity$2;But what is LeakActivity$2?.If we keep looking at our classes.

dex file you will find it there.

So let’s see the Dalvik bytecode for that class.

I have removed some code from the result that we don’t really care about.

The first interesting thing you can see is that this class implements Runnable.

# interfaces.

implements Ljava/lang/Runnable;Like I said before this class should have a reference to the outer class so where is it?.Just below the interface there is an instance field of type LeakActivity.

# instance fields.

field final synthetic this$0:Lcom/marcosholgado/performancetest/LeakActivity;And if we look at the constructor of our Runnable you will see that it takes one parameter, a LeakActivity.

method constructor <init>(Lcom/marcosholgado/performancetest/LeakActivity;)VGoing back to the bytecode of LeakActivity where we were creating a LeakActivity$2 instance, you can see how it then uses that instance (stored in v0) to invoke the constructor that we’ve just seen to pass an instance of LeakActivity.

new-instance v0, Lcom/marcosholgado/performancetest/LeakActivity$2;invoke-direct {v0, p0}, Lcom/marcosholgado/performancetest/LeakActivity$2;-><init> (Lcom/marcosholgado/performancetest/LeakActivity;)VSo our LeakActivity.

java class would indeed leak if it’s killed before the Runnable finishes because it has a reference to the activity and it would not be garbage collected at that point.

Analyzing KLeakActivity.

ktIf we now look at the Dalvik bytecode of the KLeakActivity and we focus again in the startAsyncWork method we would get the following bytecode.

In this case, rather than creating a new instance, the bytecode is doing a sget-object operation which performs an object static field operation with the identified static field.

sget-object v0, Lcom/marcosholgado/performancetest/KLeakActivity$startAsyncWork$work$1; -> INSTANCE:Lcom/marcosholgado/performancetest/KLeakActivity$startAsyncWork$work$1;Going a bit deeper into the KLeakActivity$startAsyncWork$work$1 bytecode we can see that, like before, this class implements Runnable but now it has a static method that doesn’t need an instance of the outer class.

And that’s why my KLeakActivity wasn’t really leaking anything, by using a lambda (actually a SAM) rather than an anonymous inner class I wasn’t keeping a reference to my outer activity.

But it wouldn’t be fair to say that this is something specific to Kotlin, if you are using Java8 lambdas the result is exactly the same.

If you want to know more about this, I highly recommend reading this article about lambda translations, but I will highlight this for you.

Lambdas like those in the above section can be translated to static methods, since they do not use the enclosing object instance in any way (do not refer to this, super, or members of the enclosing instance.

) Collectively, we will refer to lambdas that use this, super, or capture members of the enclosing instance as instance-capturing lambdas.

Non-instance-capturing lambdas are translated to private, static methods.

Instance-capturing lambdas are translated to private instance methodsSo what is this about?.Our Kotlin lambda is a non-instance-capturing lambda because is not using the enclosing object instance.

However if we were using let’s say a field from the outer class, our lambda would then have a reference to the outer class and leak.

In the above example we are using the field test inside our Runnable hence having a reference to the outer activity and creating a memory leak.

Looking again at the bytecode you can see how it now needs to pass an instance of KLeakActivity to our Runnable (line 9) since we are now using an instance-capture lambda.

That was everything, I hope this article will help you understand a little bit more about SAM, lambda translations and how you can safely use non-capturing lambdas without having to worry about memory leaks.

Remember that if you want to try this, all the code of this article is available at this repo.

I realize this is not a very straightforward topic so If you have any questions or if you think I messed up somewhere, please leave a comment or reach out on Twitter.

Marcos Holgado (@Orbycius) | TwitterThe latest Tweets from Marcos Holgado (@Orbycius).

Señor Android Developer for @SkySports and @SkyNews.

Public speaker…www.

twitter.

com.. More details

Leave a Reply