Binder preventing garbage collection

2019-02-10 17:10发布

问题:

I think I tracked down a memory leak and want to confirm what I think may true about how Android's Binder is implemented. In this case I have a Service and an Activity, each in their own process. I created an AIDL that allows me to pass a Callback object from the Activity to the Service through an ipc method and then have the callback called when the Service is done with the requested task.

For a long time I was wondering: if I pass a new Callback object to the Service and I don't keep a pointer to the Callback object in my Activity why doesn't the garbage collector just go ahead and collect the Callback in my Activity process? Since that doesn't seem to happen, how does the JVM know when to garbage collect the Callback in my Activity.

I think the answer is that the Binder system keeps a pointer to my Callback in the Activity process until the corresponding Callback object in the Service process has its finalize() method called, which then sends a message to the Activity to release the pointer. Is this correct? If not how does it work?

I believe it is and it leads to interesting situation where if the Callback in the Activity is pointing to something very memory intensive it won't be collected until the Callback in the Service is collected. If the Service isn't low on memory it might not collect the Callback for a long time and the Callbacks might just build up in the Activity until there is an OutOfMemoryError in the Activity.

回答1:

Yury is pretty much correct.

My Service starts a thread that holds the callback and when the thread is done with its work it calls the callback and the thread ends. When the callback is called it may do a tiny bit of work in my Activity and then return at which point I don't have pointers in my Activity process to the callback.

However the callback object in the Activity will continue to be pointed to by Android's binder system until the corresponding callback object in the Service is garbage collected.

If the callback object in the Activity process dominates some other objects that consume a lot of memory then I am wasting memory in my Activity process for no good reason and could even get an OutOfMemoryError. The solution is to create a simple method in my callback class called destory() to null out all the callback's fields and to call that method when I am done with the callback.

If the callback class is a non-static inner class you may want to consider changing it to a static inner class and passing in the parent class in the constructor, this way you can null that out as well in the destory() method.

This brings up an interesting thought, if the parent class of a non-static inner callback class is an Activity and a configuration change happens (such as a screen rotation) after the callback is sent through the binder but before it is called back then the callback will be pointing to an old Activity object when it executes!

Update: I discovered this code inside Binder.java, of course it is disabled but it would have been nice if they mentioned this kind of stuff in the Javadocs.

    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Binder> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }


回答2:

If I understand correctly how Binder works the problem in your case is the following. For each incoming incoming call your Service create a separate thread. When you pass an object to this thread your Binder system creates local copy of your object for the thread. Thus, until your Service method has returned result the thread with the copy of the object continues to work.

To check this just try to see the threads of your Service process (in DDMS).