Search code examples
javaandroidkotlinlambdagarbage-collection

Why is a Kotlin lambda being garbage collected while an object isn't?


I have a Java class that holds listeners using weak references. When I pass Kotlin lambdas as listeners, the weak reference get() method returns null. I suspect the objects are garbage-collected even though the caller still holds a reference to them. However, if I pass a regular Kotlin object instead of a lambda, this behavior doesn't occur.

  • Why is this happening?
  • Is this the expected behaviour in Kotlin?

Kotlin code:

 companion object {

    // This lambda IS being garbage collected after a while 
    val aLambda: (bundle: Bundle?, caller: Fragment?) -> Unit = { _, _ -> if (something()) processRequest() }

    // This object IS NOT being garbage collected
    val anObject = object: Notifications.Listener {
        override fun onNotification(bundle: Bundle?, caller: Fragment?) {
            if (something()) processRequest()
        }
    }

    init {
        // This Notifications class holds weak references to both the lambda & object the same way
        Notifications.addListener(aLambda)
        Notifications.addListener(anObject)
    }
}

Java code:

public class Notifications {
    
    private static ArrayList<WeakReference<Listener>> listenersArray = new ArrayList<>();

    public static void addListener(@NonNull Listener listener) {
        listenersArray.add(new WeakReference<>(listener));
    }

    public static void notify(@Nullable Bundle bundle, @Nullable Fragment caller) {
        for (WeakReference<Listener> weakListener : listenersArray) {
            Listener listener = weakListener.get();
            if (listener != null) {
                listener.onNotification(bundle, caller);
            }
        }
    }
}

Solution

  • What gets garbage-collected is not the lambda object itself, but the temporary Notifications.Listener object that the compiler automatically generates when you call this function:

    Notifications.addListener(aLambda)
    

    Since Notifications.addListener takes a parameter of type Notifications.Listener, such an object needs to be created from the given lambda (because Notifications.addListener is a functional interface). Since you're only holding this Notifications.Listener object via a WeakReference, it gets garbage-collected. The lambda itself is never garbage-collected since the property aLambda continues to hold a reference to it.

    If you define aLambda as follows instead, then its type will be what is required by addListener and it will not be garbage-collected since the Listener object will now have a strong reference held by aLambda.

    val aLambda = Notifications.Listener { _, _ -> if (something()) processRequest() }