Binder 阻止垃圾收集

发布于 2024-12-13 00:20:24 字数 651 浏览 0 评论 0原文

我想我找到了内存泄漏,并想确认我认为关于 Android Binder 的实现方式的真实情况。在本例中,我有一个服务和一个活动,每个都在自己的进程中。我创建了一个 AIDL,它允许我通过 ipc 方法将回调对象从 Activity 传递到 Service,然后在 Service 完成请求的任务时调​​用回调。

很长一段时间我都在想:如果我将一个新的 Callback 对象传递给 Service 并且我没有在 Activity 中保留指向 Callback 对象的指针为什么垃圾收集器不继续收集 Callback在我的 Activity 进程中?既然这似乎没有发生,那么 JVM 如何知道何时对我的 Activity 中的回调进行垃圾回收。

我想答案是Binder系统在Activity进程中保留了一个指向我的Callback的指针,直到Service进程中对应的Callback对象调用了其finalize()方法,然后该方法会向Activity发送消息来释放该指针。 这是正确的吗?如果不是,它是如何工作的?

我相信它是,并且它会导致有趣的情况,如果活动中的回调指向内存密集型的东西,那么在收集服务中的回调之前,它不会被收集。如果服务内存不低,它可能会在很长一段时间内不会收集回调,并且回调可能会在活动中累积,直到活动中出现 OutOfMemoryError 为止。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

最好是你 2024-12-20 00:20:24

尤里几乎是正确的。

我的服务启动一个保存回调的线程,当线程完成其工作时,它调用回调并且线程结束。当调用回调时,它可能会在我的 Activity 中执行少量工作,然后返回,此时我的 Activity 进程中没有指向回调的指针。

然而,Activity中的回调对象将继续被Android的binder系统指向,直到Service中相应的回调对象被垃圾回收为止。

如果 Activity 进程中的回调对象支配了一些消耗大量内存的其他对象,那么我就无缘无故地浪费了 Activity 进程中的内存,甚至可能会出现 OutOfMemoryError。 解决方案是在我的回调类中创建一个名为 destroy() 的简单方法,以清空所有回调的字段,并在完成回调后调用该方法。

如果回调类是非静态内部类,您可能需要考虑将其更改为静态内部类并在构造函数中传入父类,这样您也可以在 destroy() 中将其清零方法。

这带来了一个有趣的想法,如果非静态内部回调类的父类是 Activity,并且在通过绑定器发送回调之后但在回调之前发生配置更改(例如屏幕旋转),那么回调在执行时将指向旧的 Activity 对象!

更新:我在 Binder.java 中发现了这段代码,当然它被禁用了,但如果他们在 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());
        }
    }

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 destroy() 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 destroy() 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());
        }
    }
请叫√我孤独 2024-12-20 00:20:24

如果我正确理解 Binder 如何工作,您的情况下的问题如下。对于每个传入的传入呼叫,您的服务都会创建一个单独的线程。当您将对象传递给该线程时,您的 Binder 系统会为该线程创建对象的本地副本。因此,在您的 Service 方法返回结果之前,具有对象副本的线程将继续工作。

要检查这一点,只需尝试查看服务进程的线程(在 DDMS 中)。

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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文