内存泄露概述
在计算机科学中,内存泄漏(memoryleak)指由于疏忽或错误造成 程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了 对该段内存的控制,因而造成了内存的浪费
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中 分配的,使用完后必须显式释放的内存。
内存泄漏分类
• 常发性内存泄漏
• 偶发性内存泄漏
• 一次性内存泄漏
• 隐式内存泄漏
Android常见内存泄露现象
1.查询数据库没有关闭游标
Cursor cursor = getContentResolver().query(uri ...);
if (cursor.moveToNext()) {
... ...
}
修正示例代码:
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri ...);
if (cursor != null && cursor.moveToNext()) {
... ...
}
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
//ignore this
}
}
}
2.构造Adapter时,没有使用缓存的convertView
public View getView(int position, View convertView,ViewGroup parent) {
View view = newXxx(...);
... .
return view;
}
修正示例代码:
public View getView(int position, View convertView,ViewGroup parent) {
View view = null;
if (convertView !=null) {
view = convertView;
populate(view, getItem(position));
...
} else {
view = newXxx(...);
...
}
return view;
}
3. 错用静态对象
private static DrawablesBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
4. 其它
• Bitmap对象不再使用时调用recycle()释放内存
• 没有释放对象的引用
• 没有在恰当的Activity 的生命周期时间点释放资源 …
解决措施总结:
1 释放已注册的BraodcastReceiver,ContentObserver,FileObserver等
2 不要直接对Activity进行直接引用作为成员变量
3 避免循环引用
4 谨慎使用static对象
5 使用WeakReference
内存分析工具
内存分析工具-- Memory Analyzer Tool(MAT)
1 内存监测工具 -- DDMS
2 进程内存查看-- Procrank
MAT
MAT 是一个Eclipse插件,同时也有单独的RCP 客户端。
MAT介绍和详细的使用教程请参见:www.eclipse.org/mat
MAT分析过程:
1 生成.hprof 文件、
2 打开MAT 并导入.hprof文件
3 使用MAT 的视图工具分析内存
详解如何使用MAT的视图工具
• 导入.hprof 文件,点击Dominator Tree
• 按Package分组,选择自己所定义的Package 类点右键,在弹出菜单中 选择List objects->With incoming references
• 右键点击某一项可疑类,,并选择Path to GC Roots -> exclude weak/softreferences, 会进一步筛选出跟程序相关的所有有内存泄露的类。
• 据此,可以追踪到代码中的某一个产生泄露的类.
进程查看
adb shell dumpsys meminfo com.android.xxx
如何避免内存泄漏汇总
1)文件、数据库和网络操作
对文件、数据库和网络进行操作时,使用完后必须进行显示的关闭操作,释放资源。例如,在数据库操作完后,使用try … finally…确保关闭游标。
Cursor cursor= null;
try {
cursor =getContentResolver().query(uri ...);
} finally {
if (cursor != null) {
try{
cursor.close();
} catch (Exception e) {
}
}
}
这些文件、数据库和网络连接,除非其显式的关闭了连接,否则是不会自动被GC 回收,造成资源的泄露。
2)利用ListView、GridView的缓存
构造ListView、GridView等控件的Adapter时,需要利用缓存的View,减少内存占用,提高刷新效率。public View getView(int position, View convertView, ViewGroupparent) 是ListView提供每一个item所需要的view对象,可以利用缓存的convertView,而不用每次都生成一个新的View对象。例如:
public ViewgetView(int position, View convertView, ViewGroup parent) {
Viewview = null;
if(convertView != null)
view = convertView;
populate(view,getItem(position));
...
}else {
view= new Xxx(...);
...
}
returnview;
}
对于ListView、GridView中含有很多子项时,必须这样操作,否则每次都生成新的View,将耗用大量的内存。
3)使用StringBuilder类
使用合理的方式产生String对象,String类型是程序中最常用的,如果要经常对String对象的内容进行操作,推荐使用StringBuilder类这种字符串对象。例如:
String str1 =“ab” + “cd” + “ef” + “gh”;
StringBuilderbuilder = new StringBuilder ();
String str2 =builder.append(“ab”).append(“cd”).append(“ef”).append(“gh”).toString;
第一种为字符串str1赋值时要产生很多临时的字符串对象,而第二种StringBuilder是可变的字符串对象,只需生成一个对象,因此第二种方式比第一种方式占用的内存更少。
4)合理使用静态变量
静态变量是全局的,其生命周期和它所在的类是一致的,除非所在的类被释放,否则GC是不会回收该静态变量的。例如:
public class ClassName {
private static Context context;
…
}
以上的代码是很危险的,如果将Activity赋值到context的话,那么即使该Activity已经onDestroy,但是由于仍有对象持有它的引用,因此该Activity依然不会被释放。所以,应该尽量避免static成员变量引用资源耗费过多的实例。
5) Context的引用
Context尽量使用Application Context,因为这种Context 的生存周期和该应用的生存周期一样长,而不是取决于activity 的生存周期。如果你想保持一个长期生存的对象,并且这个对象需要一个Context,那么可以使用Application 这个对象。Application Context可以通过Context.getApplicationContext()或Activity.getApplication()来获取。
6) Activity中注意释放资源
Android应用程序中,在onPause()、onStop()、onDestroy()等生命周期中,需要注意相关资源的释放,保证无用的资源能够及时的回收。
7) 注册监听器与注销监听器必须成对出现
动态注册的数据库变化监听器、广播接收器等,在不使用后必须注销监听器。
8) 容器的使用
容器中的对象,不使用后,要从容器中移除,防止该对象一直被容器引用,导致无法释放。
9) 尽早释放无用对象的引用
尽量使用临时变量,让引用变量在方法退出后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。也可强制设置为空,垃圾回收会将值为null的对象作为垃圾,提高GC回收机制效率。
10) 谨慎处理对象引用
GC不能回收对象占用内存的原因就在于该对象有被引用到,在引用对象时要谨慎,要避免循环引用。另外,当一个生命周期较短的对象A,被一个生命周期较长的对象B保有其引用的情况下,在A的生命周期结束时,要在B中清除掉对A的引用。
11) 不要在经常调用的方法中创建对象
不要在循环中创建对象,适当使用Hashtable,Vector创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃。
12) Bitmap的回收
Bitmap是内存占用大户,特别是分辨率大的图片,如果要显示多张那问题就更显著了。可以说大部分的OutOfMemory,都是因为Bitmap的问题。在用完Bitmap时,要及时调用的recycle(),最好再显示的置空,以便GC能够及时回收。
Private BitmapmBitmap = Bitmap.createBitmap(480, 960, Bitmap.Config.ARGB_8888);
…
if (mBitmap !=null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
}
13) 图片资源的使用
使用图片资源时,要从源头上控制图片资源的大小,尽量使用png、9.png格式的图片,减小内存占用。
14) 避免使用AnimationDrawable
AnimationDrawable这种方式很耗内存,可以改用Handle发送消息给ImageView循环设置背景图片的方式来实现同样的动画效果,避免占用过多的内存。
15)消除冗余资源
过多的资源也会导致应用占用的内存增大,在开发时,很有必要消除那些无用的、冗余的资源文件,其中也包括冗余的代码,为APK瘦身。
16) 软引用、弱引用
对于占用内存大的对象,可以采用软引用或是弱引用的方式,软引用的对象在内存不足时释放,弱引用的对象在GC运行时释放。
A a = new A();
SoftRefenrencesr = new SoftReference(a);
B b = newB();
WeakReferencewr = new WeakReference(b);
17) 确保多分支或多出口时的资源回收
在函数中有多个分支或者多个出口时,应保证在每个分支或每个出口处都能执行到资源回收的代码,避免在某些情况下,漏掉了资源回收。
18) 大型文件或数据库一次取太多的数据导致内存溢出
开启大型文件或数据库一次取太多的数据,造成 Out OfMemory Error 的状况,需要大概计算数据量的最大值,并设定所需最小及最大的内存空间值。
19) Handle的remove操作
在有Handle处理的应用中要注意,在应用退出时要调用Handle的removeCallbacks(mRunnable)方法进行remove操作,其中mRunnable是Runnable的接口对象,并且该mRunnable对象要置空,否则会导致嵌套引用。
20)避免窗口泄露
在销毁Activity时要确保先销毁该Activity所有的附属窗口,避免Activity关闭后,其附属窗口没有关闭而造成的窗口泄露。
21) 进程自杀
由于Android系统的内存管理策略,应用程序在退出后,其进程并不会立即退出,而是要等到系统内存不足时才被结束掉。所以必要时,某些应用程序退出后可以采用自杀的方式结束该进程,即调用Process.killProcess(Process.myPid()),来节省内存空间。一般应用不需要自杀处理。
22)映射文件mmap
关闭映射内存(munmap)时,其指定的大小应该和映射文件(mmap)映射到内存大小保持一致,避免映射到共享内存对象得不到释放,而产生内存泄露。
23)Native层的使用规范
对于用C/C++开发的Native层的代码,在内存使用时,也要遵循相应的规范:malloc和free,new和delete必须配对使用,即内存分配后必须手动回收,避免内存泄露;对于刚分配到的内存,一定要进行初始化操作后才能使用;另外,要杜绝释放正在使用的内存,避免指针悬挂,同时在free或delete释放了内存之后,应将指针设置为NULL,防止产生“野指针”。
24)JNI层的使用规范
在 JNI 编程时,对于创建过多的 Local Reference,对被引用的 Java 对象操作结束后,需要调用 JNI 函数DeleteLocalRef(),及时将Local Reference删除;在使用 Global reference 时,当 native code 不再需要访问 Global reference 时,也应当调用DeleteGlobalRef() 删除 Global reference 和它引用的 Java 对象,避免内存泄露。