案例:
曾经在公司中开发一款师生同屏软件时候,需要教师端实时同步屏幕编码为H264后广播到所有的学生Android平板。在测试过程中,发现反复开启和关闭共享屏幕功能,在30次左右的时候会出现崩溃,调试后发现是由于每一次关闭的时候没有释放线程内存,导致重复创建多个线程实例,出现内存泄露甚至崩溃。
**本篇核心:Java的GC(垃圾回收机制)会自动回收内存。但是当一个对象已经不需要再使用了,本该被自动回收时,而有另外一个正在使用的对象持有它的引用,从而就导致
对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏**
java.lang.OutOfMemoryError
在Android开发中我们经常会遇到OutOfMemoryError,俗称OOM或内存溢出。而导致OOM的一个主要原因就是内存泄露(memoryLeak),简单说就是程序申请了内存,却由于某些原因没有在用完时释放此内存空间,堆积后导致OOM。在介绍内存泄露之前,我们需要了解一些背景知识。
Android系统为单个APP设置了默认内存大小,一般在16M或24M。但是现在的定制系统如小米魅族等都为单个APP设置了更高的内存,通过定制系统Linux初始化代码里面的Init.c可以查到默认的内存大小。内存泄露的原因便是APP使用的内存超过了这个阈值。
在应用层我们可以在AndroidManifest.xml中android:largeheap = “true”以提高此APP的可使用内存,但是这并不是一个友好的办法,也不能从根本解决内存泄露或者OOM。
".TestApplication"
android:allowBackup="true"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:largeHeap="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">
通过计算机基础我们知道,通常内存分配有如下3种策略
静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在。
它主要存放静态数据、全局的static数据和一些常量。
在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。
栈内存包括分配的运算速度很快,因为内置在处理器的里面的,当然容量有限。
也叫做动态内存分配。有时候可以用malloc或者new来申请分配一个内存。在C/C++可能需要自己负责释放(java里面直接依赖GC机制)。我们讨论的内存泄露主要是这个。
StrongReference,强引用:从不回收 使用:对象的一般保存 生命周期:JVM停止的时候才会终止
SoftReference,软引用:当内存不足的时候;使用:SoftReference结合ReferenceQueue构造有效期短;生命周期:内存不足时终止
WeakReference,弱引用:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止
PhatomReference 虚引用:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象呗垃圾回收期回收的活动; 生命周期:GC后终止
1)资源没有关闭(Bitmap等)导致的内存泄漏
bitmap在使用完成之后没有回收,类似的还有BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等。
解决方案:
有BraodcastReceiver在销毁时调用unregister
Cursor,Stream、File在不使用时调用close
Bitmap对象不在使用时调用recycle
2.单例模式导致内存泄露
在单例模式中如果传入Activity的Context会导致Activity销毁时无法被回收(单例还保持着引用)。
// 使用了单例模式
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
// 应该传入Application的Context
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
解决方案:单例中传入Application的Context。
注意:旋转手机的时候会不断生成MainActivity实例,在GC的时候会最多保留两个实例,而不是越来越多。
3.Handler/AsyncTask/Runnable导致内存泄露
Handler/AsyncTask/Runnable都使用匿名内部类,持有Activity的引用,在Activity销毁的时候如果任务没完成将导致无法回收Activity。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new TestRunnable()).start();
new TestAsyncTask(this).execute();
}
class TestAsyncTask extends AsyncTask<Void, Void, Void> {
}
class TestRunnable implements Runnable {
@Override
public void run() {
// ...
}
}
}。
解决方案:使用独立类而不是匿名内部类
4.ListView/GridView等导致的内存泄露
构造Adapter的时候没有复用convertView
在大多数的运行场景是属于被动型的内存管理,也就是说我们并不知道内存在那里存在泄露,那么,我们需要借助一些工具来进行内存泄露的判断。
- 使用MAT工具
MAT使用教程
- 查找引用了改对象的外部对象有哪些,研究其生命周期是否一致。
这一节到此结束,下一节我们将介绍Android的渲染优化。
==本节相关面试笔试题目:==