我一直在Android应用程序中注意到,每次退出主屏幕时,我们都会通过ByteArrayOutputStream的数量增加堆大小(泄漏)。我能够管理的最好的是添加
this.mByteArrayOutputStream = null;
在 run()的末尾,以防止堆大小不断增加。如果有人能够启发我,我会非常感激。我写了下面的例子来说明问题。
public class MainActivity extends Activity {
private Controller mController;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
@Override
protected void onStart() {
super.onStart();
this.mController = new Controller();
mController.connect();
}
@Override
protected void onStop() {
super.onStop();
mController.quit();
}
}
public class Controller {
public volatile ReaderThread mThread;
public Controller() {
super();
}
public void connect() {
mThread = new ReaderThread("ReaderThread");
mThread.start();
}
public void quit() {
mThread.quit();
}
public static class ReaderThread extends Thread {
private volatile boolean isProcessing;
private ByteArrayOutputStream mByteArrayOutputStream;
public ReaderThread(String threadName) {
super(threadName);
}
@Override
public void run() {
this.isProcessing = true;
Log.d(getClass().getCanonicalName(), "START");
this.mByteArrayOutputStream = new ByteArrayOutputStream(2048000);
int i = 0;
while (isProcessing) {
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 = false;
}
}
}
答案 0 :(得分:8)
线程对GC免疫,因为它们是garbage collection roots。因此,JVM可能会将您的ReaderThread
保留在内存中,以及它对成员变量的分配,从而造成泄漏。
正如您所指出的那样,取消ByteArrayOutputStream
会使GC的缓冲数据(但不是ReaderThread
本身)可用。
修改强>:
经过一番调查,我们了解到the Android debugger was causing the perceived leak:
VM保证调试器知道的任何对象在调试器断开连接之前不会被垃圾回收。 在调试器连接时,这会导致对象随时间累积。例如,如果调试器看到正在运行的线程,则即使在线程终止后,关联的Thread对象也不会被垃圾回收。
答案 1 :(得分:3)
来自Android Debugging页面:
调试器和垃圾收集器目前是松散集成的。 VM保证调试器不知道的任何对象 收集垃圾,直到调试器断开连接。这个可以 调试器时,会导致对象随时间累积 连接的。例如,如果调试器看到一个正在运行的线程,那么 即使在之后,关联的Thread对象也不会被垃圾回收 线程终止。
你认为这会降低DDMS堆监控的价值吗?
答案 2 :(得分:2)
这个问题非常有趣。我看了一下它,并设法找到一个没有泄漏的工作解决方案。我用它们的原子对应物替换了volatile变量。我还使用了AsyncTask而不是Thread。这似乎已经成功了。没有更多的泄漏。
<强>代码强>
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;
}
}
}
这是两个代码样本在进出主屏幕几次后的比较。您可以在第一个上看到byte []泄漏。
至于为什么会发生这种情况,谷歌建议您使用AsyncTask或Service进行异步操作。我记得他们的一个视频明确建议反对线程。进行演示的人警告副作用。我猜这是其中之一?我可以清楚地看到,当活动恢复生命时会发生泄漏,但没有解释为什么会发生泄漏(至少在JVM上。我不知道dalvik VM的内部结构以及何时发生认为线程完全停止了)。我尝试了弱引用/归零控制器/等等,这些方法都没有奏效。
请参阅此答案,了解报告此泄漏的原因 - https://stackoverflow.com/a/12971464/830964。这是误报。
我能够摆脱BAOS占用的内存而不用异步任务显式地将其清空。它也适用于其他变量。如果能为您解决,请告诉我。
答案 3 :(得分:1)
根据您使用的代码,ByteArrayOutputStream
实例只有在ReaderThread
实例泄漏时才会泄漏。只有当线程仍然在运行,或者某个地方仍然存在可访问的引用时,才会发生这种情况。
专注于搞清楚 ReaderThread
实例泄漏的方式。
答案 4 :(得分:1)
我有类似的问题,我通过将线程归零来解决它。
我已经在模拟器(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,因此不会泄漏。