内存管理的目的就是让咱们在开发过程当中有效避免咱们的应用程序出现内存泄露的问题。内存泄露相信你们都不陌生,咱们能够这样理解:「没有用的对象没法回收的现象就是内存泄露」。android
若是程序发生了内存泄露,则会带来如下这些问题git
应用可用的内存减小,增长了堆内存的压力github
下降了应用的性能,好比会触发更频繁的 GC设计模式
严重的时候可能会致使内存溢出错误,即 OOM Error性能优化
OOM 发生在,当咱们尝试进行建立对象,可是堆内存没法经过 GC 释放足够的空间,堆内存也没法再继续增加,从而完成对象建立请求的时候,OOM 发生颇有多是内存泄露致使的,但并不是全部的 OOM 都是由内存泄露引发的,内存泄露也并不必定引发 OOM。bash
若是真的想比较清楚的了解内存泄露的话,对于 Java 的内存管理以及引用类型有一个清晰的认识是必不可少的。网络
理解 Java 的内存管理能让咱们更深一层地了解 Java 虚拟机是怎样使用内存的,一旦出现内存泄露,咱们也能更加从容地排查问题。框架
了解 Java 的引用类型,能让咱们更加理解内存泄露出现的缘由,以及常见的解决方法。ide
具体的内容,能够看下这篇文章 你真的懂 Java 的内存管理和引用类型吗?函数
单例模式是很是经常使用的设计模式,使用单例模式的类,只会产生一个对象,这个对象看起来像是一直占用着内存,但这并不意味着就是浪费了内存,内存原本就是拿来装东西的,只要这个对象一直都被高效的利用就不能叫作泄露。
可是过多的单例会让内存占用过多,并且单例模式因为其 静态特性,其生命周期 = 应用程序的生命周期,不正确地使用单例模式也会形成内存泄露。
举个例子:
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context;
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
复制代码
上面是一个比较简单的单例模式用法,须要外部传入一个 Context 来获取该类的实例,若是此时传入的 Context 是 Activity 的话,此时单例就有持有该 Activity 的强引用(直到整个应用生命周期结束)。这样的话,即便该 Activity 退出,该 Activity 的内存也不会被回收,这样就形成了内存泄露,特别是一些比较大的 Activity,甚至还会致使 OOM(Out Of Memory)。
解决方法: 单例模式引用的对象的生命周期 = 应用生命周期
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context.getApplicationContext();
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
复制代码
能够看到在 SingleInstanceTest 的构造函数中,将 context.getApplicationContext() 赋值给 mContext,此时单例引用的对象是 Application,而 Application 的生命周期原本就跟应用程序是同样的,也就不存在内存泄露。
这里再拓展一点,不少时候咱们在须要用到 Activity 或者 Context 的地方,会直接将 Activity 的实例做为参数传给对应的类,就像这样:
public class Sample {
private Context mContext;
public Sample(Context context){
this.mContext = context;
}
public Context getContext() {
return mContext;
}
}
// 外部调用
Sample sample = new Sample(MainActivity.this);
复制代码
这种状况若是不注意的话,很容易就会形成内存泄露,比较好的写法是使用弱引用(WeakReference)来进行改进。
public class Sample {
private WeakReference<Context> mWeakReference;
public Sample(Context context){
this.mWeakReference = new WeakReference<>(context);
}
public Context getContext() {
if(mWeakReference.get() != null){
return mWeakReference.get();
}
return null;
}
}
// 外部调用
Sample sample = new Sample(MainActivity.this);
复制代码
被弱引用关联的对象只能存活到下一次垃圾回收以前,也就是说即便 Sample 持有 Activity 的引用,但因为 GC 会帮咱们回收相关的引用,被销毁的 Activity 也会被回收内存,这样咱们就不用担忧会发生内存泄露了。
咱们先来看看非静态内部类(non static inner class)和 静态内部类(static inner class)之间的区别。
class 对比 | static inner class | non static inner class |
---|---|---|
与外部 class 引用关系 | 若是没有传入参数,就没有引用关系 | 自动得到强引用 |
被调用时须要外部实例 | 不须要 | 须要 |
可否调用外部 class 中的变量和方法 | 不能 | 能 |
生命周期 | 自主的生命周期 | 依赖于外部类,甚至比外部类更长 |
能够看到非静态内部类自动得到外部类的强引用,并且它的生命周期甚至比外部类更长,这便埋下了内存泄露的隐患。若是一个 Activity 的非静态内部类的生命周期比 Activity 更长,那么 Activity 的内存便没法被回收,也就是发生了内存泄露,并且还有可能发生难以预防的空指针问题。
举个例子:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
复制代码
能够看到咱们在 Activity 中继承 AsyncTask 自定义了一个非静态内部类,在 doInbackground() 方法中作了耗时的操做,而后在 onCreate() 中启动 MyAsyncTask。若是在耗时操做结束以前,Activity 被销毁了,这时候由于 MyAsyncTask 持有 Activity 的强引用,便会致使 Activity 的内存没法被回收,这时候便会产生内存泄露。
解决方法: 将 MyAsyncTask 变成非静态内部类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
复制代码
这时候 MyAsyncTask 再也不持有 Activity 的强引用,即便 AsyncTask 的耗时操做还在继续,Activity 的内存也能顺利地被回收。
匿名类和非静态内部类最大的共同点就是 都持有外部类的引用,所以,匿名类形成内存泄露的缘由也跟静态内部类基本是同样的,下面举个几个比较常见的例子:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ① 匿名线程持有 Activity 的引用,进行耗时操做
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// ② 使用匿名 Handler 发送耗时消息
Message message = Message.obtain();
mHandler.sendMessageDelayed(message, 60000);
}
复制代码
上面举出了两个比较常见的例子
new 出一个匿名的 Thread,进行耗时的操做,若是 MainActivity 被销毁而 Thread 中的耗时操做没有结束的话,便会产生内存泄露
new 出一个匿名的 Handler,这里我采用了 sendMessageDelayed() 方法来发送消息,这时若是 MainActivity 被销毁,而 Handler 里面的消息还没发送完毕的话,Activity 的内存也不会被回收
解决方法:
继承 Thread 实现静态内部类
继承 Handler 实现静态内部类,以及在 Activity 的 onDestroy() 方法中,移除全部的消息 mHandler.removeCallbacksAndMessages(null);
集合类添加元素后,仍引用着集合元素对象,致使该集合中的元素对象没法被回收,从而致使内存泄露,举个例子:
static List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object obj = new Object();
objectList.add(obj);
obj = null;
}
复制代码
在这个例子中,循环屡次将 new 出来的对象放入一个静态的集合中,由于静态变量的生命周期和应用程序一致,并且他们所引用的对象 Object 也不能释放,这样便形成了内存泄露。
解决方法: 在集合元素使用以后从集合中删除,等全部元素都使用完以后,将集合置空。
objectList.clear();
objectList = null;
复制代码
除了上述 3 种常见状况外,还有其余的一些状况
一、须要手动关闭的对象没有关闭
二、static 关键字修饰的成员变量
三、ListView 的 Item 泄露
除了必须了解常见的内存泄露场景以及相应的解决方法以外,掌握一些好用的工具,能让咱们更有效率地解决内存泄露的问题。
Lint 是 Android Studio 提供的 代码扫描分析工具,它能够帮助咱们发现代码机构 / 质量问题,同时提供一些解决方案,检测内存泄露固然也不在话下,使用也是很是的简单,能够参考下这篇文章:Android 性能优化:使用 Lint 优化代码、去除多余资源
LeakCanary 是 Square 公司开源的「Android 和 Java 的内存泄漏检测库」,Square 出品,必属精品,功能很强大,使用也很简单。建议直接看 Github 上的说明:leakcanary,也能够参考这篇文章:Android内存优化(六)LeakCanary使用详解