为什么会产生内存泄漏?
Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但有另外一个正在使用的对象持有它的引用,从而导致它不能回收停留在堆内存中,这就产生了内存泄漏。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。
内存泄露对程序产生的影响?
内存泄漏是造成应用程序OOM的主要原因之一。Android系统为每个应用程序分配有限的内存,当应用中内存泄漏较多时,就难免会导致应用所需要的内存超出系统分配限额,从而导致OOM应用Crash;
Android常见的内存泄露
1,单例造成:由于单例静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象(如Context)已经不使用了,而单例对象还持有对象的引用造成这个对象不能正常被回收;
2,非静态内部类创建静态实例造成:在Acitivity内存创建一个非静态内部类单例,避免每次启动资源重新创建。但是因为非静态内部类默认持有外部类(Activity)的引用,并且使用该类创建静态实例。造成该实例和应用生命周期一样长,导致静态实例持有引用的Activity和资源不能正常回收;
3,Handler造成:子线程执行网络任务,使用Handler处理子线程发送消息。由于handler对象是非静态匿名内部类的对象,持有外部类(Activity)的引用。在Handler-Message中Looper线程不断轮询处理消息,当Activity退出还有未处理或者正在处理的消息时,消息队列中的消息持有handler对象引用,handler又持有Activity,导致Activity的内存和资源不能及时回收;
4,线程造成:匿名内部类Runnalbe和AsyncTask对象执行异步任务,对当前Activity隐式引用。当Activity销毁之前,任务还没有执行完,将导致Activity的内存和资源不能及时回收;
5,资源未关闭造成的内存泄露:对于使用了BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄露;
内存泄漏与内存溢出的区别是什么?
内存泄漏: 垃圾对象依旧占据内存,如水龙头的泄漏,水本来是属于水源的, 但是水龙头没关紧,那么泄漏到了水池;再来看内存,内存本来应 该被回收,但是依旧在内存堆中;总结一下就是内存存在于不该存在的地方(没用的地方)
内存溢出: 内存占用达到最大值,当需要分配内存时,已经没有内存可以分配了,就是溢出;依旧以水池为例, 水池的水如果满了,那么如果继 续需要从水龙头流水的话,水就会溢出。总结一下就是,内存的分配超出最大阀值,导致了一种异常
明白了两者的概念,那么两者有什么关系呢?
内存的溢出是内存分配达到了最大值,而内存泄漏是无用内存充斥了内存堆;因此内存泄漏是导致内存溢出的元凶之一,而且是很大的元凶;因为内存分配完后,哪怕占用再大,也会回收,而泄漏的内存则不然;当清理掉无用内存后,内存溢出的阀值也会相应降低。
LeakCanary简介
LeakCanary是Square公司开源的一个检测内存的泄露的函数库,可以方便地和你的项目进行集成,在Debug版本中监控Activity、Fragment等的内存泄露;
LeakCanary集成到项目中之后,在检测到内存泄露时,会发送消息到系统通知栏。点击后打开名称DisplayLeakActivity的页面,并显示泄露的跟踪信息,Logcat上面也会有对应的日志输出。同时如果跟踪信息不足以定位时,DisplayLeakActivity还为开发者默认保存了最近7个dump文件到App的目录中,可以使用MAT等工具对dump文件进行进一步的分析;
如何使用leakcanary检测内存泄露
1,在android studio的build.gradle中引用leakcanary
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
2,自定义Application,并在onCreate方法中执行以下代码:
public class QAplication extends Application{
@Override
public void onCreate() {
super.onCreate();
... ...
//初始化LeakCanary
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
3,AndroidManifest.xml中添加权限
<!--SDCard中创建与删除文件权限-->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!--向SDCard写入数据权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
已经结束了!通过以上配置,你就可以轻松使用LeakCanary检测内存泄漏了,当然这种简单的配置仅限于在Activity中进行检测,当然还存在其他类的内存泄漏,如Fragment等,这时我们就需要使用RefWatcher来进行监控了,具体见下面在非Activity的其它类中使用RefWatcher来进行内存泄漏的监控的讲解。
单例内存泄露模拟
1.单例如下:
public class TestManager {
//单例静态特性使得单例的生命周期和应用的生命周期一样长
private static TestManager instance;
private Context context;
/**
* 传入的Context的生命周期很重要:
* 如果传入的是Application的Context,则生命周期和单例生命周期一样长;
* 如果传入的是Activity的Context,由于该Context和Activity的生命周期一样长,当Activity退出的时候它的内存不会被回收,因为单例对象持有它的引用;
*/
private TestManager(Context context) {
this.context = context;
}
public static TestManager getInstance(Context context) {
if (instance == null) {
instance = new TestManager(context);
}
return instance;
}
}
2,在activity中创建该单例对象:
public class LeakCanaryActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leakcanary);
//获取单例对象,退出Activity即可模拟出内存泄露
TestManager testManager = TestManager.getInstance(this);
}
}
运行App到LeakCanaryActivit页面并退出,在检测到内存泄露的时候,会发送消息到系统通知栏;
点击通知消息,打开名为DisplayLeakActivity的页面,并显示泄漏的跟踪信息;
在非Activity的其它类中使用RefWatcher来进行内存泄漏的监控
第二节的例子代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。改写Application,如下所示。
public class LeakApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher= setupLeakCanary();
}
private RefWatcher setupLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
LeakApplication leakApplication = (LeakApplication) context.getApplicationContext();
return leakApplication.refWatcher;
}
}
install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。
最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LeakThread leakThread = new LeakThread();
leakThread.start();
}
class LeakThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(6 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = LeakApplication.getRefWatcher(this); //1
refWatcher.watch(this);
}
}
MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。
在注释1处得到RefWatcher,并调用它的watch方法,watch方法的参数就是要监控的对象。当然,在这个例子中onDestroy方法是多余的,因为LeakCanary在调用install方法时会启动一个ActivityRefWatcher类,它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露。这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。
运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来,比如三星S8的通知栏如下所示。
Notification中提示了MainActivity发生了内存泄漏, 泄漏的内存为787B。点击Notification就可以进入内存泄漏详细页,除此之外也可以通过Leaks应用的列表界面进入,列表界面如下图所示。
内存泄漏详细页如下图所示。
点击加号就可以查看具体类所在的包名称。整个详情就是一个引用链:MainActiviy的内部类LeakThread引用了LeakThread的this$0,this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的MainActiviy的实例,这将会导致MainActivity无法被GC,从而产生内存泄漏。