Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory leak in Java, but not in Kotlin (of same code base)... why? [duplicate]

I have a piece of simple code below in an activity...

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

            }
        });
        valueAnimator.start();
    }
}

If the activity got terminated, there will be memory leak (as proven by Leak Canary).

However, when I covert this code to identical Kotlin code (using shift-alt-command-k), it is as below

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
        valueAnimator.repeatCount = ValueAnimator.INFINITE
        valueAnimator.addUpdateListener { }
        valueAnimator.start()
    }
}

Memory leak no longer happen. Why? Is it because the anonymous class object got converted to Lambda?

like image 833
Elye Avatar asked Jan 08 '18 12:01

Elye


2 Answers

The difference between these 2 versions is pretty simple.

Java version of the AnimatorUpdateListener contains implicit reference to the outer class (MainActivity in your case). So, if the animation keeps running when the activity is not needed anymore, the listener keeps holding the reference to the activity, preventing it from being garbage-collected.

Kotlin tries to be more clever here. It sees that the lambda which you pass to the ValueAnimator does not reference any objects from the outer scope (i.e. MainActivity), so it creates a single instance of AnimatorUpdateListener which will be reused whenever you [re]start the animation. And this instance does not have any implicit references to the outer scope.

Side note: if you add the reference to some object from outer scope to your lambda, Kotlin will generate the code which creates a new instance of the update listener every time the animation is [re]started, and these instances will be holding the implicit references to MainActivity (required in order to access the object(s) which you decide to use in your lambda).

Another side note: I strongly recommend to read the book called "Kotlin in Action" as it contains lots of useful information on Kotlin in general, and my explanation of how Kotlin compiler make a choice about whether to put the implicit reference to outer scope into the object created after SAM conversion or not comes from this book.

like image 162
aga Avatar answered Oct 14 '22 11:10

aga


1. Check what's actually going on

I think you'll find the “Show Kotlin Bytecode” view very helpfull in seeing exactly what is going on. See here for the InteliJ shortcut. (Would have done this for you, but hard without greater context of your application)

2. JVM similarities, but Kotlin explicit differences

But since Kotlin runs on the same JVM as Java (and so uses the same garbage collector as Java) you should expect a similarly safe runtime environment. That being said, when it comes to Lamdas and explicit references, when they're converted. And it can vary in different cases:

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.

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

3. Summary, and further reading

This answer should explain what your seeing, couldn't have explained it better myself: https://stackoverflow.com/a/42272484/979052 Different question, I know, but the theory behind it is the same - hope that helps

like image 33
Alicia Avatar answered Oct 14 '22 10:10

Alicia