【问题标题】:Why won't my Threads die and cause a memory leak?为什么我的线程不会死掉并导致内存泄漏?
【发布时间】:2013-04-25 08:47:58
【问题描述】:

我的一个应用正在积累大量的Thread 实例,GC 无法拾取并清除这些实例。从长远来看,这种内存泄漏会使应用程序崩溃。

我不确定它们来自哪里100%,但我有一种明显的感觉,以下可能是有问题的代码:

public class UraHostHttpConnection extends AbstractUraHostConnection {
    private Handler uiThreadHandler = new Handler(Looper.getMainLooper());
    private Executor taskExecutor = new Executor() {
         public void execute(Runnable command) {
             new Thread(command).start();
        }
    };
    private ConnectionTask task = null;

    @Override
    public void sendRequest(final HttpUriRequest request) {
        this.task = new ConnectionTask();
        this.uiThreadHandler.post(new Runnable() {
            public void run() {
                task.executeOnExecutor(taskExecutor, request);
            }
        });
   }

    @Override
    public void cancel() {
        if (this.task != null)
            this.task.cancel(true);
    }
}

此代码允许我在默认的AsyncTask Executor(这只是一个单线程队列)上并行运行多个不会相互阻塞的 HTTP 连接。

我检查过,AsyncTasks 实际上已经达到了他们的onPostExecute() 方法,并且不会永远运行。在检查了一些内存转储后,我怀疑包装 Thread-Objects 在 AsyncTasks 完成后不会停止运行。

上面的代码是否仍然是我的内存泄漏的原因,还是我应该开始寻找其他地方?

感谢任何帮助。

编辑: 应该注意的是,sendRequest 只被调用一次。上面示例中没有的代码的其他部分确保了这一点。

编辑 2: 超类如下所示:

public abstract class AbstractUraHostConnection {
    protected IUraHostConnectionListener listener = null;

    public void setListener(IUraHostConnectionListener listener) {
        this.listener = listener;
    }
    public abstract void sendRequest(HttpUriRequest request);
    public abstract void cancel();
}

AsyncTask 看起来像这样:

private class ConnectionTask extends AsyncTask<HttpUriRequest, Object, Void> {
    final byte[] buffer = new byte[2048];
    private ByteArrayBuffer receivedDataBuffer = new ByteArrayBuffer(524288);

    @Override
    protected Void doInBackground(HttpUriRequest... arg0) {
        UraHostHttpConnection.taskCounter++;
        AndroidHttpClient httpClient = AndroidHttpClient.newInstance("IVU.realtime.app");
        try {
            // Get response and notify listener
            HttpResponse response = httpClient.execute(arg0[0]);
            this.publishProgress(response);

            // Check status code OK before proceeding
            if (response.getStatusLine().getStatusCode() == 200) {
                HttpEntity entity = response.getEntity();
                InputStream inputStream = entity.getContent();
                int readCount = 0;

                // Read one kB of data and hand it over to the listener
                while ((readCount = inputStream.read(buffer)) != -1 && !this.isCancelled()) {
                    this.receivedDataBuffer.append(buffer, 0, readCount);
                    if (this.receivedDataBuffer.length() >= 524288 - 2048) {
                        this.publishProgress(receivedDataBuffer.toByteArray());
                        this.receivedDataBuffer.clear();
                    }
                }

                if (this.isCancelled()) {
                    if (arg0[0] != null && !arg0[0].isAborted()) {
                        arg0[0].abort();
                    }
                }
            }
        } catch (IOException e) {
            // forward any errors to listener
            e.printStackTrace();
            this.publishProgress(e);
        } finally {
            if (httpClient != null)
                httpClient.close();
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Object... payload) {
        // forward response
        if (payload[0] instanceof HttpResponse)
            listener.onReceiveResponse((HttpResponse) payload[0]);
        // forward error
        else if (payload[0] instanceof Exception)
            listener.onFailWithException((Exception) payload[0]);
        // forward data
        else if (payload[0] instanceof byte[])
            listener.onReceiveData((byte[]) payload[0]);
    }

    @Override
    protected void onPostExecute(Void result) {
        listener.onReceiveData(this.receivedDataBuffer.toByteArray());
        listener.onFinishLoading();
        UraHostHttpConnection.taskCounter--;
        Log.d(TAG, "There are " + UraHostHttpConnection.taskCounter + " running ConnectionTasks.");
    }
}

【问题讨论】:

  • 不太确定,但这对您有帮助吗? androiddesignpatterns.com/2013/04/…
  • AbstractUraHostConnection 的超类的构造函数中有什么东西可能会吓到吗? ConnectionTask 的外观如何?
  • 添加了两个类的代码。

标签: android multithreading android-asynctask executor


【解决方案1】:

将 ThreadPoolExecutor 替换为您的 Executor,以便您可以控制池的大小。如果 ThreadPoolExecutor 基本上是一个暴露了方法的 Executor,那可能只是默认最大池大小设置得很高的情况。

官方文档here.

特别看一下:

setCorePoolSize(int corePoolSize)
//Sets the core number of threads.

setKeepAliveTime(long time, TimeUnit unit)
//Sets the time limit for which threads may remain idle before being terminated.

setMaximumPoolSize(int maximumPoolSize)
//Sets the maximum allowed number of threads.

如果您想减少代码,还有一个替代方案(更好的主意取决于您真正想要多少控制以及您将交易多少代码来获得它)。

Executor taskExecutor = Executors.newFixedThreadPool(x);

其中 x = 池的大小

【讨论】:

  • 谢谢。是的,这解决了问题。但出于好奇,我想知道我的代码出了什么问题。一段时间后杀死无响应的线程并不是一个特别优雅的解决方案:-)
  • 已编辑答案以供替代。线程应该自动进行 GC,但正如我们所知,GC 会在需要时执行 GC 所做的事情,即使我们直接询问也是如此。我希望我对底层机制有更多的了解,但他们不会无缘无故地称我为 Kludge……此外,限制池应该让你不必自己追捕僵尸。
猜你喜欢
  • 2014-06-15
  • 2013-07-12
  • 1970-01-01
  • 2017-02-13
  • 1970-01-01
  • 1970-01-01
  • 2019-01-31
  • 1970-01-01
相关资源
最近更新 更多