Java 为什么Android上的线程会泄漏?

Java 为什么Android上的线程会泄漏?,java,android,multithreading,garbage-collection,Java,Android,Multithreading,Garbage Collection,我在我们的Android应用程序中注意到,每次我们退出主屏幕时,我们都会通过ByteArrayOutputStream的数量来增加堆大小(泄漏)。我能做的最好的事情就是添加 this.mByteArrayOutputStream = null; 在run()的末尾,以防止堆大小不断增加。如果有人能启发我,我将非常感激。我写了下面的例子来说明这个问题 MainActivity.java Controller.java 根据您所使用的代码,ByteArrayOutputStream实例只有在Rea

我在我们的Android应用程序中注意到,每次我们退出主屏幕时,我们都会通过ByteArrayOutputStream的数量来增加堆大小(泄漏)。我能做的最好的事情就是添加

this.mByteArrayOutputStream = null;
在run()的末尾,以防止堆大小不断增加。如果有人能启发我,我将非常感激。我写了下面的例子来说明这个问题

MainActivity.java Controller.java
根据您所使用的代码,
ByteArrayOutputStream
实例只有在
ReaderThread
实例泄漏时才会泄漏。只有当线程仍在运行,或者在某个地方仍有可访问的引用时,才会发生这种情况


专注于找出
ReaderThread
实例如何泄漏。

线程对GC免疫,因为它们是安全的。因此,JVM很可能将您的
ReaderThread
以及它对成员变量的分配保存在内存中,从而造成泄漏

正如您所注意到的,将
ByteArrayOutputStream
置零将使其缓冲数据(而不是
读线程
本身)可用于GC

编辑

经过一些调查,我们了解到:

VM保证在调试器断开连接之前,不会对调试器知道的任何对象进行垃圾收集这可能会导致在连接调试器时,随着时间的推移,对象不断累积。例如,如果调试器看到一个正在运行的线程,即使在线程终止后,关联的线程对象也不会被垃圾收集


这个问题很有趣。我看了一下,设法找到了一个没有泄漏的有效解决方案。我用原子变量替换了可变变量。我还使用了异步任务而不是线程。这似乎成功了。没有更多的泄漏

代码

class Controller {
    public ReaderThread mThread;
    private AtomicBoolean isProcessing = new AtomicBoolean(false);
    public Controller() {
        super();
    }

    public void connect() {
        ReaderThread readerThread = new ReaderThread("ReaderThread",isProcessing);
        readerThread.execute(new Integer[0]);
    }

    public void quit() {
        isProcessing.set(false);
    }

    public static class ReaderThread extends AsyncTask<Integer, Integer, Integer> {
        private AtomicBoolean isProcessing;
        private ByteArrayOutputStream mByteArrayOutputStream;

        public ReaderThread(String threadName, AtomicBoolean state) {
            this.isProcessing = state;
        }

        public void run() {
            this.isProcessing.set(true);

            Log.d(getClass().getCanonicalName(), "START");
            this.mByteArrayOutputStream = new ByteArrayOutputStream(2048000);

            int i = 0;
            while (isProcessing.get()) {
                Log.d(getClass().getCanonicalName(), "Iteration: " + i++);
                mByteArrayOutputStream.write(1);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }

            try {
                mByteArrayOutputStream.reset();
                mByteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            Log.d(getClass().getCanonicalName(), "STOP");
        }

        public void quit() {
            this.isProcessing.set(false);
        }

        @Override
        protected Integer doInBackground(Integer... params)
        {
            run();
            return null;
        }
    }
}
类控制器{
公共读取器线程mThread;
私有AtomicBoolean isProcessing=新的AtomicBoolean(false);
公共控制员(){
超级();
}
公共void connect(){
ReaderThread ReaderThread=新的ReaderThread(“ReaderThread”,isProcessing);
execute(新整数[0]);
}
公开作废退出(){
isProcessing.set(false);
}
公共静态类ReaderThread扩展异步任务{
私有原子处理;
私有ByteArrayOutputStream mbyterayoutputstream;
public ReaderThread(字符串threadName,原子布尔状态){
this.isProcessing=状态;
}
公开募捐{
this.isProcessing.set(true);
Log.d(getClass().getCanonicalName(),“START”);
this.mbyterarayoutputstream=新的ByteArrayOutputStream(2048000);
int i=0;
while(isProcessing.get()){
d(getClass().getCanonicalName(),“迭代:”+i++);
mbyterarayoutputstream.write(1);
试一试{
睡眠(1000);
}捕捉(中断异常e){
}
}
试一试{
mByteArrayOutputStream.reset();
mByteArrayOutputStream.close();
}捕获(IOE异常){
e、 printStackTrace();
}
Log.d(getClass().getCanonicalName(),“STOP”);
}
公开作废退出(){
this.isProcessing.set(false);
}
@凌驾
受保护的整数doInBackground(整数…参数)
{
run();
返回null;
}
}
}
这是两个代码示例在主屏幕上来回运行几次后的比较。您可以在第一个上看到字节[]泄漏

至于为什么会发生这种情况,谷歌建议您使用异步任务或服务进行异步操作。我记得他们的一个视频明确建议不要使用线程。做报告的人警告说有副作用。我猜这就是其中之一?我可以清楚地看到,当活动恢复生命时,存在泄漏,但没有解释为什么会发生泄漏(至少在JVM上是这样。我不知道dalvik VM的内部以及它认为线程已完全停止的时间)。我尝试了弱引用/清空控制器/etc,但这些方法都不起作用

有关报告泄漏的原因,请参阅此答案-。这是假阳性


我能够摆脱BAOS占用的内存,而不用异步任务显式地将其置零。它也适用于其他变量。让我知道这是否为您解决了问题。

我遇到了类似的问题,我通过将线程置零来解决它

我已经在emulator(SDK 16)中尝试了您的代码,更改
quit()
方法如下:

public void quit() { 
    mThread.quit(); 
    mThread = null;  // add this line
} 
我已在DDMS中检查泄漏是否有效停止。拆下管路后,泄漏会再次出现

--编辑--

我也在一个使用SDK 10的真实设备上尝试过这一点,结果如下

您是在测试代码中还是在完整代码中尝试了线程置零?它确保测试代码在线程为空后不会泄漏,无论是在真实设备中还是在模拟器中

初始启动后的屏幕截图(分配7.2MB/使用:4.6MB):

两次重启后的屏幕截图(分配7.2MB/使用:4.6MB):

多次快速重启后的屏幕截图,以及多次旋转设备(分配13.2MB/使用:4.6MB):

虽然在最后一个屏幕中分配的内存明显高于初始内存,但分配的内存仍为4.6MB,因此不会泄漏。

从页面:

调试器和垃圾收集器目前是松散集成的。 VM保证调试器知道的任何对象都不存在 垃圾收集到去毛刺之后
class Controller {
    public ReaderThread mThread;
    private AtomicBoolean isProcessing = new AtomicBoolean(false);
    public Controller() {
        super();
    }

    public void connect() {
        ReaderThread readerThread = new ReaderThread("ReaderThread",isProcessing);
        readerThread.execute(new Integer[0]);
    }

    public void quit() {
        isProcessing.set(false);
    }

    public static class ReaderThread extends AsyncTask<Integer, Integer, Integer> {
        private AtomicBoolean isProcessing;
        private ByteArrayOutputStream mByteArrayOutputStream;

        public ReaderThread(String threadName, AtomicBoolean state) {
            this.isProcessing = state;
        }

        public void run() {
            this.isProcessing.set(true);

            Log.d(getClass().getCanonicalName(), "START");
            this.mByteArrayOutputStream = new ByteArrayOutputStream(2048000);

            int i = 0;
            while (isProcessing.get()) {
                Log.d(getClass().getCanonicalName(), "Iteration: " + i++);
                mByteArrayOutputStream.write(1);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }

            try {
                mByteArrayOutputStream.reset();
                mByteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            Log.d(getClass().getCanonicalName(), "STOP");
        }

        public void quit() {
            this.isProcessing.set(false);
        }

        @Override
        protected Integer doInBackground(Integer... params)
        {
            run();
            return null;
        }
    }
}
public void quit() { 
    mThread.quit(); 
    mThread = null;  // add this line
}