Android Out of Memory Error:Causes, Solution and Best practices
译文:http://blog.csdn.net/isesar/article/details/19339293(它来自 http://blogs.innovationm.com/android-out-of-memory-error-causes-solution-and-best-practices/)
当你开发一个要处理multipleimages sets or large bitmaps or some Animation stuff的应用时,内存不足的错误是非常常见的。在这种情况下,在处理图像或对象的分配和释放时我们必须非常小心、高效的。如果分配超过堆限制或者你的进程需求的内存超过堆限制,则会发生OOM错误。
在Android中,每个应用程序都在Linux进程运行。 每个Linux进程都有一个虚拟机(Dalvik虚拟机)在其里面运行。 每个进程对内存的请求都有一个极限,并且在不同的设备之间极限值不同,同样在不同的手机和平板电脑之间极限值也不同。当某个进程需要比它的极限更高的内存,它会导致一个错误,即内存不足(OOM)的错误。
可能的原因:
有很多原因,我们会得到一个内存不足的错误。其中一些是:
1,你正在做一些操作,不断需要大量的内存,在某些时候它超越了一个进程的最大堆内存限制。
2,你是泄露一些内存即你没有让你分配符合垃圾回收(GC)上一个对象。这就是所谓的内存泄漏。
3,你正在处理大量位图和加载所有的人都在运行时。 你必须通过加载,你不需要整个位图一次的大小,然后做缩放处理非常仔细地与大位图。
对于内存不足最大的原因是内存泄漏。首先让我们看看内存泄漏的例子:
示例:
1,我们做一个Activity,并创建Activity上下文的一个TextView并设置到它的示例文本。
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
setContentView(label);
}
这意味着,view有引用到整个activity,并且因此引用到任何持有该activiy的对象; 通常整个视图层次结构和它的所有资源。因此,如果您泄漏了Context(“泄漏”,意思是你保持对它的引用,从而阻止来自GC收集它),你泄漏了大量的内存。如果你不小心很容易泄露整个Activity。
2,当屏幕方向改变,系统将默认情况下,在保留其状态的同时,销毁当前Activity,并创建一个新的。在这样做时,Android将重新从资源中加载应用程序的UI。想象一下,你写一个应用程序,拥有一个大的bitmap,你肯定不希望每次旋转都重新加载。最简单的方法的去保持它,不必重新加载它在每次旋转屏幕的时候,是保持在一个静态字段:
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
ImageView label = new ImageView(this);
if(sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
此代码速度非常快,也非常错误的; 它泄露了创建在第一次屏幕方向变化之前的activity。当绘制对象附加到一个oiew,该view被设置为绘制对象一个回调。在上面的代码片断中,这意味着绘制对象具有一个对ImageView对象引用,而imageView对象对activity具有一个引用(The Context),这反过来引用了相当多的东西(取决于你的代码)。
现在,如果屏幕方向改变了,绘制对象将不会被GC,因为它是静态的。并且由于这个,ImageView的也不会被GC,因为该绘制对象是由绘制对象的回调连接到的ImageView;对Activity context也是如此,因为ImageView对象是由Activity context创建的。因此,这将导致内存泄漏,并在每次更改屏幕方向时,这些泄漏将在堆中累加,在某些时候,它会增加堆限制、Android将杀死应用和抛出内存错误(OOM)
诊断OOM的工具
在Android中有一些可以用来查找内存泄漏的工具,它们中的一个是MAT(Memory Analyzer Tool)。MAT可以作为Eclipse的一个插件或作为一个独立的工具。
让我们开始安装MAT,并且在一步一步的指导下在Eclipse下使用MAT :
1,要启动这个工具,你必须从以下链接下载该工具: http://www.eclipse.org/mat/downloads.php
2,启动Eclipse和运行你的应用程序。
3,进入DDMS,选择您的应用程序,然后单击Dump HPROF文件按钮,它会告诉你保存HPROF文件。将其保存在硬盘上。
4,运行位于在你下载的MAT包下MemoryAnalyzer.exe文件。
5, MAT工具不会理解Eclipse生成的HPROF文件,你可以使用HPROF-CONV工具(在android-sdk的tools文件夹中)使用以下命令将其转换:
hprof-convcom.example.android.hcgallery.hprof mat.hprof
6,现在这个文件已经准备好给MAT使用。
如何在MAT中分析Heap dump?
案例研究
在做Android应用程序开发中,我也发生过内存不足的错误(OOM)。在下面的例子中,我将分享我的问题陈述、我发现泄漏以及如何解决它的方式。
问题陈述:
我用多个绘制对象(Drawables ),使用AnimationFragment类做一个帧动画,它工作正常,只是在旋转屏幕时,我没有检查Heap(堆)。每次我被旋转设备,Activity将重新启动以加载新的布局为横向模式,因此,堆将增长。这样一直下去,将会发生OOM错误。
原因:
事实上有一个Context 泄露在我的fragment中,每次我旋转设备以前的Context没有被销毁,因为在AnimaitonDrawable类中使用的可绘制对象仍保持回调到AnimationDrawable,阻止activity得到销毁或被GC回收。
我是如何发现问题的原因?
1, 我从我的应用程序得到了dump,并使用HPROF-CONV转换,然后用MAT打开“mat.hprof” 文件。
2,观察各选项,并选中选项中消费最高的选项,它会看起来像下面:
此图将告诉你每一个对象(窗口小部件,可绘制对象(drawables),位图等)占用的分配和空间。使用此您可以检查哪些对象占用的内存比较多,并且你可以查看代码,先解决他们。在我的例子中,占用内存较多的是AnimatioinDrawable。
图中有两个所示的heap,让我们看看他们先:
Shallow Heap:这是变量实际上用到的字节大小。
Retained Heap:这是变量与被关联到的引用的字节大小。
为了更直观的表现请看下图:
对于A, Shallow Heap是100,但Retained Heap是300,因为它持有300字节的内存要被GC回收。
在第2个图像,我们可以清楚地看到,AnimationDrawable对象是在堆中的最大对象。所以,我们首先应该针对AnimationDrawable。要看到实际的对象和它的位置,点击相应行,并进入ListObjects- >与传入的引用,然后你会看到下面的窗口。
Expand thisentry to see all the objects and in this case the entries are
Retained Heap对查找泄漏非常重要,它是所有对象要求维持活动支配的总大小的统治者。为了得到这个数字,MAT需要通过以下所有的对象引用来计算保留大小。正如Retained Heap排序对查找泄漏非常有帮助,MAT会显示最大Retained Heap在一个饼图。
选定的是我的代码中对象,其是具有高retainedheap值和条目显示为包名和类名。现在,你可以明确的知道是那些对象占用大部分内存,你可以根据此检查代码。
解决方案 :
我使用MAT检查了我的代码,然后我意识到AnimationDrawable并没有被销毁掉,因此AnimationDrawable里面的frames也成为了泄漏的一部分。所以,我清除了AnimationDrawable的所有回调,只要Fragment 被破坏,在此之后heap稳定,并且当屏幕方向改变heap 减少,因为可绘对象(Drawables )得到破坏,然后增加因为新的分配。(So, i clear all the callbacks of AnimationDrawableas soon as the Fragment is destroyed and after this heap was stable and whenorientation changes the heap decreases as the Drawables got destroyed and thanincreases for the new allocation.)翻译的不好
OOM产生的主要原因大多是泄露。 所以,如果更接近泄漏,你可以很容易地摆脱OOM的。(There can be a no. of reasons for a OOM but themain reasons mostly ends in a Leak. So if get closer to the leaks than you caneasily get rid of OOM)翻译的不好
获取一个运行时的Heapdump:
当你得到一个OOM错误,要检查堆和分配时的错误,你可以添加一些代码,来捕获的OOM错误,然后调用调试器,从我们的代码中转储堆(Heap dump)
还是让我告诉你一个例子,来做到这一点:
1, 写一个内部类实现Thread.UncaughtExceptionHandler来作为OOM错误的监听器
public static class MyUncaughtExceptionHandler
implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread thread, Throwableex) {
if(ex.getClass().equals(OutOfMemoryError.class)) {
try {
android.os.Debug.dumpHprofData("/sdcard/dump.hprof");
} catch (IOException e) {
e.printStackTrace();
}
}
ex.printStackTrace();
}
}
2,在你的Activity的OnCreate()方法中设置一个监听在当前线程上,将捕获的OOM错误
@Override
publicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setDefaultUncaughtExceptionHandler(
new MyUncaughtExceptionHandler());
}
通过这种方式,您可以在OOM的发生时的点检查Heap Dump
避免内存泄漏或OOM的最佳实践
• 不要保持长期引用一个Activity/Context(一个Activity的引用应该具有与Activity本身相同的生命周期)
• 尝试使用应用程序(application)的Context而不是Activity的Context
• 如果在你的应用程序中,你正在使用一个大的位图作为背景或其他,请不要把所有的位图放到主存储器(don’t pull the full image in to the main memory)。 您可以使用位图的insample大小属性,为屏幕带来屏幕所需的size。
• 记忆碎片:你应该计划的变量的分配,使释放后的内存不应该分散或碎片。如果有10MB的可用内存块,并且进程(process)需求的是2MB,然而找不到的2 MB的一大块内存,然后它会要求更多的堆,这反过来又可能导致OOM。
• 发送大文件到服务器应该在内存块(chunks)完成,因为以字节为单位加载一个大的图像可能会导致OOM。
• 一个Activity正在使用这么多的图像,应删除绘制所使用的回调,用setCallBack(null) on the drawable,以避免OOM的绘制。