1 内存泄漏简介

内存泄漏是指内存空间使用完毕后无法被释放的现象。尽管Java有垃圾回收机制(GC),但是对于还保持着引用,逻辑上却已经不会再用到的对象,垃圾回收器不会回收它们。

内存泄漏带来的危害:

  • 用户对单次的内存泄漏并没有什么感知,但当可用的空闲空间越来越少,GC就会更容易被触发,GC进行时会停止其他线程的工作,因此有可能会造成界面卡顿等情况。
  • 后续需要分配内存的时候,很容易导致内存空间不足而出现 OOM(内存溢出)。

2 常见的内存泄漏场景

2.1 static 关键字修饰成员变量

被 static 关键字修饰的成员变量的生命周期等于应用程序的生命周期。若使被 static 关键字修饰的成员变量引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期大于引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露。

  • static Activity

    这里也会提示有内存泄漏。
  • static View
    如果一个 View 初始化耗费大量资源,而且在一个 Activity 生命周期内保持不变,那可以把它变成 static,加载到视图树上(View Hierachy)。当 Activity 被销毁时,应当释放资源,否则就会导致内存泄漏。

解决方案:

  1. 尽量避免 static 成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Applicaiton的 Context。
  2. 使用弱引用(WeakReference) 代替强引用持有实例。

2.2 非静态内部类/ 匿名类

非静态内部类 / 匿名类默认持有外部类的引用,而静态内部类则不会。常见的情况有以下三种。

2.2.1 非静态内部类

如果非静态内部类所创建的实例是静态的,其生命周期等于应用的生命周期。非静态内部类默认持有外部类的引用而导致外部类无法释放,最终造成内存泄露。即外部类中持有非静态内部类的静态对象。

public class MainActivity extends AppCompatActivity {//非静态内部类的静态实例引用public static InnerClass innerClass = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//保证非静态内部类的实例只有1个if (innerClass == null) {innerClass = new InnerClass();}}// 非静态内部类private class InnerClass {//...}
}

当 MainActivity 销毁时,因非静态内部类单例的引用,innerClass 的生命周期等于应用的生命周期,持有外部类 MainActivity 的引用,故 MainActivity 无法被 GC 回收,从而导致内存泄漏。

解决方案:

  1. 将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)
  2. 该内部类抽取出来封装成一个单例
  3. 尽量避免非静态内部类所创建的实例是静态的。

2.2.2 多线程:AsyncTask、实现 Runnable 接口、继承 Thread 类

当工作线程正在处理任务时,如果外部类销毁, 由于工作线程实例持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄露。

2.2.2.1 AsyncTask

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);startAsyncTask();}private void startAsyncTask() {new AsyncTask<Void, Void, Void>() {@Overrideprotected Void doInBackground(Void... voids) {//执行耗时操作while(true);}}.execute();}
}

2.2.2.2 实现 Runnable 接口

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}class MyRunnable implements Runnable {@Overridepublic void run() {//执行耗时操作}}
}

2.2.2.3 继承 Thread 类

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);new MyThread().start();}private class MyThread extends Thread {@Overridepublic void run() {//执行耗时操作}}
}

解决方案:

  1. 使用静态内部类的方式,静态内部类不默认持有外部类的引用。
    private static class MyThread extends Thread {@Overridepublic void run() {//执行耗时操作}}
  1. 当外部类结束生命周期时,强制结束线程。使得工作线程实例的生命周期与外部类的生命周期同步。
    @Overrideprotected void onDestroy() {super.onDestroy();myThread.interrupt();}

2.3 Handler

在 Handler 消息队列还有未处理的消息 / 正在处理消息时,消息队列中的 Message 持有 Handler 实例的引用。如果 Handler 是非静态内部类 / 匿名内部类(2种使用方式),就会默认持有外部类的引用(如 MainActivity 实例)。


上述的引用关系会一直保持,直到 Handler 消息队列中的所有消息被处理完毕。在 Handler 消息队列还有未处理的消息 / 正在处理消息时,此时若需销毁外部类 MainActivity,但由于上述引用关系,垃圾回收器(GC)无法回收 MainActivity,从而造成内存泄漏。

public class MainActivity extends AppCompatActivity {private MyHandler myHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);myHandler = new MyHandler();new Thread() {@Overridepublic void run() {try {//执行耗时操作Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}//发送消息myHandler.sendEmptyMessage(1);}}.start();}private class MyHandler extends Handler {@Overridepublic void handleMessage(Message msg) {//处理消息事件}}
}

解决方案:

  1. 使用静态内部类+弱引用的方式,保证外部类能被回收。因为弱引用的对象拥有短暂的生命周期,在垃圾回收器线程扫描时,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
public class MainActivity extends AppCompatActivity {private MyHandler myHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);myHandler = new MyHandler(this);new Thread() {@Overridepublic void run() {try {//执行耗时操作Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}//发送消息myHandler.sendEmptyMessage(1);}}.start();}public void test() {Log.d("MainActivity", "test");}private static class MyHandler extends Handler {//定义弱引用实例private WeakReference<Activity> reference;//在构造方法中传入需持有的Activity实例public MyHandler(Activity activity) {//使用 WeakReference 弱引用持有 Activity 实例reference = new WeakReference<Activity>(activity);}@Overridepublic void handleMessage(Message msg) {//处理消息事件//调用Activity实例中的方法((MainActivity) reference.get()).test();}}
}
  1. 当外部类结束生命周期时,清空 Handler 内消息队列。

使用建议:
为了保证 Handler 中消息队列中的所有消息都能被执行,此处推荐使用解决方案1,即静态内部类+弱引用的方式。

2.4 资源对象使用后未关闭

对于资源的使用(如广播 BraodcastReceiver、文件流 File、数据库游标 Cursor、图片资源 Bitmap等),若在 Activity 销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏。

解决方案:

//对于广播BroadcastReceiver:注销注册
unregisterReceiver(broadcastReceiver);//对于文件流File:关闭流
inputStream / outputStream.close();//对于数据库游标cursor:使用后关闭游标
cursor.close();//对于图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null
bitmap.recycle();
bitmap = null;// 对于动画(属性动画),将动画设置成无限循环播放setRepeatCount(ValueAnimator.INFINITE);后
// 在Activity退出时记得停止动画
animator.cancel();

关闭以上对象的时候注意做非空判断。

2.5 WebView内存泄露

WebView 内部的一些线程持有 Activity 对象,使得 Activity 无法释放,从而导致内存泄漏。

解决方案:

    @Overrideprotected void onDestroy() {if (mWebView != null) {ViewParent parent = mWebView.getParent();if (parent != null) {((ViewGroup) parent).removeView(mWebView);}mWebView.stopLoading();mWebView.getSettings().setJavaScriptEnabled(false);mWebView.clearHistory();mWebView.clearView();mWebView.removeAllViews();mWebView.destroy();mWebView = null;}super.onDestroy();}

不建议在 xml 中创建 WebView,因为在 xml 中创建的 WebView 会持有 Activity 的 Context 对象。

2.6 单例模式造成的内存泄漏

由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。

public class Singleton {private static Singleton instance;private Context mContext;private Singleton(Context context){this.mContext = context;}public static Singleton getInstance(Context context){if (instance == null){synchronized (Singleton.class){if (instance == null){instance = new Singleton(context);}}}return instance;}
}

这是一个单例模式,当创建这个单例的时候,由于需要传入一个 Context:

  1. 如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这没有问题。
  2. 如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 的内存并不会被回收,这就造成泄漏了。

解决方案:
将 new Singleton(context) 改为 new Singleton(context.getApplicationContext()) 即可,这样便和传入的 Activity 没关系了。

public class Singleton {private static Singleton instance;private Context mContext;private Singleton(Context context){this.mContext = context;}public static Singleton getInstance(Context context){if (instance == null){synchronized (Singleton.class){if (instance == null){instance = new Singleton(context.getApplicationContext());// 使用Application 的context}}}return instance;}
}

3 内存泄漏分析工具

3.1 lint

lint 是一个静态代码分析工具,同样也可以用来检测部分会出现内存泄露的代码,平时编程注意 lint 提示的各种黄色警告即可。如:

也可以手动检测,在 Android Studio 中选择 Analyze->Inspect Code。


然后会弹出弹窗选择检测范围。

点击 OK 等待分析结果:


这个工具除了会检测内存泄漏,还会检测代码是否规范、是否有没用到的导包、可能的bug、安全问题等等。

3.2 Memory Profile

Memory Profile 的使用

3.3 LeakCanary

LeakCanary 的使用

Android 内存泄露分析相关推荐

  1. JVM内存管理概述与android内存泄露分析

    一.内存划分 将内存划分为六大部分,分别是PC寄存器.JAVA虚拟机栈.JAVA堆.方法区.运行时常量池以及本地方法栈. 1.PC寄存器(线程独有):全称是程序计数寄存器,它记载着每一个线程当前运行的 ...

  2. Android内存泄漏分析

    内存泄漏指的是程序中不再使用的对象对象由于某些原因无法被正常GC回收.对象没 有及时释放,就会占据宝贵的内存空间,因而导致后续分配内存的时候,内存空间不足出现OOM.如果无用对象占据的控件越大,那么可 ...

  3. Android内存泄露测试不再蓝瘦,香菇

    在进行Android内存泄露分析时,面对成千上万个对象,你是否蓝瘦,香菇?作为测试人员你在进行内存泄露测试之后,是否有勇气告诉开发同事程序已经没有内存泄露,可以放心发布了? 众所周知,内存泄露测试难点 ...

  4. Android内存泄漏分析及调试

    2019独角兽企业重金招聘Python工程师标准>>> Android内存泄漏分析及调试 分类: Android2013-10-25 11:31 5290人阅读 评论(5) 收藏 举 ...

  5. Android内存泄露抓取工具leakcanary

    引言 "A small leak will sink a great ship." - Benjamin Franklin 概述 某些对象的生命周期有限,当它们的工作完成以后,将会 ...

  6. Android内存泄露和GC机制

    Android内存泄露和GC机制 本文先对Android内存垃圾回收机制进行介绍,之后对分析.定位内存泄露常用的测试方法进行总结,分享给大家. 一.Android内存垃圾回收(GC机制) 1.综述 A ...

  7. android释放acitity内存,Android 内存泄漏分析与解决方法

    在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...

  8. java内存泄露分析方案

    java内存泄露分析方案 - 准备工作 1.工具:Memory Analyzer Tool (mat); 1)安装Memory Analyzer Tool (mat) 2.原料:dump.hprof ...

  9. 一次.net托管内存泄露分析

    简介:一次.net托管内存泄露分析 最近协助分析了一个.net进程内存泄露的问题,过程分享给大家. 症状:客户的服务端.net进程出现分钟级的cpu抖动,接近100%后落回. 图1 分析:支持同学通过 ...

最新文章

  1. 对于索引(a,b,c),下列哪些说法是正确的
  2. 城市追风口,车企“缉拿”路测牌照
  3. LeetCode 673. Number of Longest Increasing Subsequence--O(N log N )--Java,C++,Python解法
  4. win7下一次加载和调试sys驱动程序的过程以及捕捉到内核打印字符串函数的输出
  5. c语言位运算负数的实例_一招教你学会C语言中位运算
  6. v8学习笔记(七) 执行过程
  7. java安全编码指南之:锁的双重检测
  8. reverseajax(comet) socket 杂记
  9. linux make命令_第一章 1.3Linux下安装Redis
  10. @ font-face 引入本地字体文件
  11. 算法:整数除法上取整
  12. atitit 指令集概论原理导论 艾提拉著 目录 2. 2.3 CISC和RISC 复杂指令集 1 1. 指令集(IA:InstructionSet)是指CPU指令系统所能识别(翻译)执行的全部指令
  13. 魔兽世界各服务器显示版本,魔兽7.1各服通用界面AltzUI
  14. Revit二次开发——依据两条平曲线创建一条三维曲线
  15. 海马玩安卓模拟器-安装流程详解
  16. 01 数据库和MySQL简介
  17. loss.backward(),scheduler(), optimizer.step()的作用
  18. 计算机辅助设计绘图员技能鉴定试题(建筑类),计算机辅助设计高级绘图员技能鉴定试题...
  19. 【自学笔记】尚硅谷数据结构与算法Chapter 4 栈
  20. Psychtoolbox刺激呈现方式

热门文章

  1. vue :to设置路由导航的用法
  2. 上海旅游-徐家汇教堂
  3. WebStorm 的全局搜索字符串
  4. 房价这么高,为什么租金却高不起来?
  5. HDU-1814-TwoSAT
  6. c语言实现图片缩放,图片缩放与合并(C语言实现)
  7. 通俗易懂聊springMVC中的handler是什么
  8. 2018.09.26朴素贝叶斯算法研究日志
  9. 智能优化算法——正余弦优化算法(SCA)及其改进策略
  10. (四)sql多表连接查询join on的用法