我正在编写一个简单的地图应用程序,从服务器下载osm磁贴,为每个磁贴生成一个新的AsyncTask。一些AsyncTasks运行完成,一些在它们有机会运行之前被取消(如果我平移屏幕)。当它们似乎泄漏时可能是。我首先通过检查我的瓷砖位图的寿命并看到它们泄漏来发现这一点。在对AsyncTask中的位图(间接)引用进行归零后,位图停止泄漏,但它让我思考为什么所述引用很重要并且发现AsycTasks的数量不断增长,尽管所有努力都要关闭对它们的引用。我运行MAT分析,这是我需要帮助的时候。任何人都可以帮我解释image(对不起,我没有足够的stackoverflow布朗点来直接附加图像)。
显示的树条目显示了许多泄露的AsyncTasks(类型为FileCacheTask)中的一个,其子项表示对它的所有传入引用。我知道'指示'是垃圾收集器。另外两个,表示为$ 0(暗示与外层的亲和关系)类型android.os.AsyncTask(这与FileCacheTask的外部类没有任何关系,不可否认,FileCacheTask是tile工厂内部的非静态内部类) ,通过mWorker成员持有对我泄露的对象的引用,是我无法解释的,显然无法摆脱。我尝试按照路径并查找对android.os.AsyncTask对象的传入引用,但看到一些调度内部和我的代码没有任何关系)。 有什么想法可能是android.os.AsyncTask对象,这个对象被称为$ 0?
修改即可。 根据建议,我(有点)将问题提炼为可消化的代码片段。我说的有点,因为它看似表现出相同的行为,但我不能确定它真的遭受同样的问题。它仍然表现得密码。这是代码:
public class TaskLeak {
static public int DDeletedTasks; //these are all for debug/tracing purposes
static public int DCreatedTasks;
static public int DCancelled;
private class PrivateTask extends AsyncTask<Void, Void, Void>
{
private int mTaskId;
private boolean mFinished;
private Bitmap mBitmap;
public PrivateTask()
{
mTaskId = ++DCreatedTasks;
//allocating a bitmap to give this object a meaningful weight for GC to consider.
mBitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888);
//Log.d("Tasks", "(constructor) Task count:"+ mTaskId+" tasks in existence:"+(DCreatedTasks-DDeletedTasks));
execute();
}
@Override
protected Void doInBackground(Void... params) {
try {
int sleepForMs = mRandomGenerator.nextInt(100);
//emulating blocking download action for a random few milliseconds.
Thread.sleep(sleepForMs);
//Log.d("Tasks", "Waking up from sleep, task:" + mTaskId);
} catch (InterruptedException e) {
//Log.d("Tasks", "Can't sleep, task:" + mTaskId);
}
return null;
}
@Override
protected void onPostExecute(Void result)
{
mFinished = true;
maintain();
}
@Override
protected void onCancelled()
{
DCancelled++;
onPostExecute(null);
}
@Override
protected void finalize()
{
++DDeletedTasks;
Log.d("Tasks", "(destructor) Task count:"+ mTaskId+" tasks in existence:"+(DCreatedTasks-DDeletedTasks));
}
public boolean finished()
{
return mFinished;
}
}
private final static int KMaxTasks = 100;
private Random mRandomGenerator;
private final int mMaxCycles;
private int mCycles;
private final Vector<PrivateTask> mTasks;
public TaskLeak(int maxCycles)
{
mMaxCycles = maxCycles;
mRandomGenerator = new Random();
mTasks = new Vector<PrivateTask>();
for (int i = 0; i < KMaxTasks; i++)
{
mTasks.add(new PrivateTask());
}
}
public void maintain()
{
Log.d("Tasks", "***maintain(), tasks held:"+mTasks.size()+" there are "+ (mMaxCycles - ++mCycles)+" cycles left to go");
for (int i = 0; i < mTasks.size();)
{
if (mTasks.get(i).finished())
{
mTasks.remove(i);
if (mCycles < mMaxCycles)
{
mTasks.add(new PrivateTask());
}
}
else
{
i++;
}
}
if ((mCycles % 10) == 0 )
{
DCancelled++;
mTasks.get(5).cancel(true);
}
if (mTasks.size() == 0)
{
new TestMemoryGrabber(200).test();
Log.d("Tasks", "maintain() has finished!");
Log.d("Tasks", "maintain(). There have been "+DCreatedTasks+" tasks created.");
Log.d("Tasks", "maintain(). There have been "+DDeletedTasks+" tasks deleted.");
Log.d("Tasks", "maintain(). There have been "+(DCreatedTasks-DDeletedTasks)+" tasks remaining.");
Log.d("Tasks", "maintain(). There have been "+DCancelled+" tasks cancelled.");
}
}
当执行1000个循环时,它会在内存异常中途中途(在循环#570上)失败,尽管在任何时候我都维持不超过100个对PrivateTask对象的引用。这本身就有些令人费解,因为GC应该不断为新条目腾出空间。为什么不呢?
这是关于oom异常何时发生的LogCat:
01-12 16:37:50.902: D/Tasks(3235): (destructor) Task count:568 tasks in existence:156
01-12 16:37:50.993: D/Tasks(3235): ***maintain(), tasks held:100 there are 430 cycles left to go
01-12 16:37:51.062: I/dalvikvm-heap(3235): Clamp target GC heap from 49.251MB to 48.000MB
01-12 16:37:51.062: D/dalvikvm(3235): GC_FOR_ALLOC freed 257K, 2% free 48337K/48903K, paused 70ms, total 71ms
01-12 16:37:51.062: D/Tasks(3235): (destructor) Task count:569 tasks in existence:156
01-12 16:37:51.092: D/Tasks(3235): ***maintain(), tasks held:100 there are 429 cycles left to go
01-12 16:37:51.222: I/dalvikvm-heap(3235): Clamp target GC heap from 49.251MB to 48.000MB
01-12 16:37:51.232: D/dalvikvm(3235): GC_FOR_ALLOC freed 257K, 2% free 48337K/48903K, paused 139ms, total 139ms
01-12 16:37:51.242: D/Tasks(3235): ***maintain(), tasks held:100 there are 428 cycles left to go
01-12 16:37:51.312: I/dalvikvm-heap(3235): Clamp target GC heap from 49.502MB to 48.000MB
01-12 16:37:51.322: D/dalvikvm(3235): GC_FOR_ALLOC freed <1K, 1% free 48593K/48903K, paused 70ms, total 72ms
01-12 16:37:51.322: I/dalvikvm-heap(3235): Forcing collection of SoftReferences for 262160-byte allocation
01-12 16:37:51.412: I/dalvikvm-heap(3235): Clamp target GC heap from 49.494MB to 48.000MB
01-12 16:37:51.412: D/dalvikvm(3235): GC_BEFORE_OOM freed 9K, 1% free 48584K/48903K, paused 86ms, total 86ms
01-12 16:37:51.412: E/dalvikvm-heap(3235): Out of memory on a 262160-byte allocation.
01-12 16:37:51.412: I/dalvikvm(3235): "main" prio=5 tid=1 RUNNABLE
01-12 16:37:51.412: I/dalvikvm(3235): | group="main" sCount=0 dsCount=0 obj=0x40a14568 self=0x2a00b9e0
01-12 16:37:51.412: I/dalvikvm(3235): | sysTid=3235 nice=0 sched=0/0 cgrp=apps handle=1073870640
01-12 16:37:51.412: I/dalvikvm(3235): | schedstat=( 21236163363 7517071458 4002 ) utm=1940 stm=183 core=0
01-12 16:37:51.412: I/dalvikvm(3235): at android.graphics.Bitmap.nativeCreate(Native Method)
01-12 16:37:51.412: I/dalvikvm(3235): at android.graphics.Bitmap.createBitmap(Bitmap.java:640)
01-12 16:37:51.412: I/dalvikvm(3235): at android.graphics.Bitmap.createBitmap(Bitmap.java:620)
答案 0 :(得分:0)
在查看了您的代码,日志和解释后,我猜这个OutOfMemoryError是由于在内存中存储了太多位图而导致的。
当您创建每个PrivateTask时,在它开始运行之前,您正在初始化要保留在内存中的Bitmap。有超过100个任务存在,你正在推动你的内存限制。 Logcat行如下所示:
01-12 16:37:51.412: D/dalvikvm(3235): GC_BEFORE_OOM freed 9K, 1% free 48584K/48903K, paused 86ms, total 86ms
至于缓解问题,我建议的第一个解决方案是确保一旦取消每个任务,就在你的Bitmap对象上明确调用recycle()
。我怀疑即使PrivateTasks被取消,也会保留一些对它们的引用,这导致只有非常少量(1%ish)的内存才能在GC发生时被释放,因为系统不是摧毁它们,或者反过来,你的位图。或者,GC可以正确地处理这个问题,但是在您的活动PrivateTasks中有足够的位图将您推送到内存限制,这也会导致没有任何垃圾收集,因为系统无法找到任何要收集的内容!如果您经常需要从服务器下载如此多的位图,您可能需要考虑将它们存储在磁盘上,并且只在需要时将它们检索到内存中,尽管这可能会导致显着的性能损失。
以下是有关位图管理的一些链接和其他SO问题:
Displaying Bitmaps Efficiently from the Android Dev Site(特别参见缓存位图一节)
Solutions for someone who had a similar problem
More info on what's going on under the hood when you allocate memory for a bitmap.
作为旁注,我很好奇你正在测试什么类型的设备/模拟器,尤其是SDK版本。随着更新的硬件,为Android应用程序提供的内存量增加,因此您可能会在不同的手机上看到不同的结果测试。此外,我仍然认为许多设备(越来越多的设备,如Gingerbread失去市场份额和ICS / Jellybean收益)将可能连续执行这些操作,这也将改变您获得的结果,尽管似乎不是这个特定问题的根源,将更加关注您应用的未来兼容性。
希望这有帮助!