Android内存泄漏分析
内存泄漏指的是程序中不再使用的对象对象由于某些原因无法被正常GC回收。对象没
有及时释放,就会占据宝贵的内存空间,因而导致后续分配内存的时候,内存空间不足出现OOM。如果无用对象占据的控件越大,那么可分配的空闲空间就越小,GC会更容易被处罚,而GC时会短暂停止其他线程,因而可能引起卡顿等现象。
Java内存分配策略
JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存。
- 方法区
主要存放静态数据、全局static数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。 - 虚拟机栈
当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。 - 堆
是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。
public class Sample {int s1 = 0;Sample mSample1 = new Sample();public void method() {int s2 = 1;Sample mSample2 = new Sample();}
}
Sample mSample3 = new Sample();
局部变量s2和引用变量mSample2都位于栈中,但是mSample2指向的对象是存在于堆上的;
mSample3保存于栈中,而其指向的对象实体存放在堆上,包括这个对象的所有成员变量s1和mSample1。
Java是如何管理内存
Java的内存管理就是对象的分配和释放问题。在Java中,通过关键字new为每个对象申请内存空间,所有的对象都在堆(Heap)中分配空间,对象的释放是由GC决定和执行的。
GC(Garbage Collection) 即垃圾回收机制,在Java虚拟机上运行的一个程序,它会监控对象的使用,将不再使用的对象释放,回收内存。
GC 要做的三件事
- 那些内存需要回收?
- 什么时候回收?
- 怎么回收?
有两种常用的方法确定哪些对象是垃圾需要被回收 1.引用计数法2.可达性分析
- 引用计数法
在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数都为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。 - 可达性分析
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收(如下图黑圈)。
在Java语言中,可以作为GC Roots的对象有如下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI(Native方法)引用的对象。
Java中的四种引用类型
在Java中,将引用方式分为:强引用、软引用、弱引用、虚引用,这四种引用强度依次逐渐减弱。
- 强引用
类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。 - 软引用
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。 - 弱引用
弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。 - 虚引用
虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。
内存泄漏的场景
静态变量内存泄漏
静态变量的生命周期跟整个程序的生命周期一致。只要静态变量没有被销毁也没有置为null,其对象就一直被保持引用,也就不会被垃圾回收,从而出现内存泄漏。
// MainActivity.java
public class MainActivity extends AppCompatActivity {private static Test sTest;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sTest = new Test(this);}
}
// Test.java
public class Test {private Context context;public Test(Context context) {this.context = context;}
}
说明:sTest作为静态变量,并且持有Activity的引用,sTest的生命周期肯定比Activity的生命周期长。因此当Activity退出后,由于Activity仍被sTest引用到,所以Activity就不能被回收,造成了内存泄漏。
解决方案:
- 针对静态变量
在不使用静态变量时置为空,如:
sTest = null;
- 针对Context
如果用到Context,尽量去使用Application的Context,避免直接传递Activity,如:
sTest = new Test(getApplicationContext());
- 针对Activity
若一定要使用Activity,建议使用弱引用或软引用来代替强引用。如:
// 弱引用
WeakReference<Activity> weakReference = new WeakReference<>(this);
Activity activity = weakReference.get();// 软引用
SoftReference<Activity> softReference = new SoftReference<>(this);
Activity activity = softReference.get();
单例内存泄漏
单例模式其生命周期跟应用一样,所以使用单例模式时传入的参数需要注意一下,避免传入Activity等对象造成内存泄漏。
public class AppManager {private static AppManager instance;private Context context;private AppManager(Context context) {this.context = context;}public static AppManager getInstance(Context context) {if (instance == null) {instance = new AppManager(context);}return instance;}
}
说明:当创建这个单例对象的使用,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要;
- 如果传入的是Application的Context,因为Application的生命周期就是整个应用的生命周期,所以这将没有任何问题。
- 如果传入的是Activity的Context,当这个Context所对应的Activity退出时,由于该Context的引用被单例所持有,其生命周期等于整个应用程序的生命周期,所以当前Activity退出时它的内存并不会被回收,这就造成泄漏了。
解决方案:
使用和单例生命周期一样的对象。
public class AppManager {private static AppManager instance;private Context context;private AppManager(Context context) {this.context = context.getApplicationContext(); // 使用Application的context}public static AppManager getInstance(Context context) {if (instance == null) {instance = new AppManager(context);}return instance;}
}
非静态内部类(匿名类)内存泄漏
非静态内部类(匿名类)默认就持有外部类的引用,当非静态内部类(匿名类)对象的生命周期比外部类对象的生命周期长时,就会导致内存泄漏。
Handler内存泄漏
如果Handler中有延迟任务或者等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。
首先,非静态的Handler类会默认持有外部类的引用,如Activity等。
然后,还未处理完的消息(Message)中会持有Handler的引用。
还未处理完的消息会处于消息队列中,即消息队列MessageQueue会持有Message的引用。
消息队列MessageQueue位于Looper中,Looper的生命周期跟应用一致。
引用链:Looper -> MessageQueue -> Message -> Handler -> Activity
解决方法:
- 静态内部类+弱引用
静态内部类默认不持有外部类的引用,所以改成静态内部类即可。同时,可以采用弱引用来持有Activity的引用。(也可以使用WeakHandler库:https://github.com/badoo/android-weak-handler)
private static class MyHandler extends Handler {private WeakReference<Activity> mWeakReference;public MyHandler(Activity activity) {mWeakReference = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);//...}
}
- Activity退出时,移除所有信息
移除信息后,Handler将会跟Activity生命周期同步。
@Override
protected void onDestroy() {super.onDestroy();mHandler.removeCallbacksAndMessages(null);
}
多线程引起的内存泄漏
匿名Thread类里持有外部类的引用。当Activity退出时,Thread有可能还在后头执行,这时就会发生内存泄露。
new Thread(new Runnable() {@Overridepublic void run() {}
}).start();
解决方法:
- 静态内部类
静态内部类不持有外部类的引用。
private static class MyThread extends Thread {// ...
}
- Activity退出时,结束线程
这是让线程的生命周期跟Activity一致。
集合类内存泄漏
集合类添加元素后,将会持有元素对象的引用,导致该元素对象不能被垃圾回收,从而发生内存泄漏。
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {Object obj = new Object();objectList.add(obj);obj = null;
}
说明:虽然obj已经被置为空了,但是集合里还是持有Object的引用。
解决方法:
- 清空集合对象
objectList.clear();
objectList = null;
未关闭资源对象内存泄漏
一些资源对象需要在不使用的时候主动去关闭或者注销掉,否则的话,他们不会被垃圾回收,从而造成内存泄漏。
注销监听器
当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要将自己注册到服务的监听器中,然而,这会让服务持有Activity的引用,如果忘记Activity销毁时取消注册,就会导致Activity泄露。
关闭输入输出流
在使用IO、File流等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果不及时关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。
inputStream.close();
outputStream.close();
回收Bitmap
Bitmap对象比较占内存,当它不再被使用的时候,最好调用Bitmap.recycle()方法主动进行回收。
bitmap.recycle();
bitmap = null;
停止动画
属性动画中有一类无限动画,如果Activity退出时不停止动画的话,动画会一直执行下去。因为动画会持有View的引用,View又持有Activity,最终Activity就不能被回收掉。只要我们在Activity退出把动画停止掉即可。
animation.cancel();
销毁WebView
WebView在加载网页后会长期占用内存而不能被释放,因此在Activity销毁后要调用它的destory()方法来销毁它以释放内存。此外,WebView在Android 5.1上也会出现其他的内存泄露。
@Override
protected 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();}super.onDestroy();
}
内存分析工具
dumpsys
dumpsys命令可以查看内存使用情况。
adb shell dumpsys meminfo <packageName>
** MEMINFO in pid 22215 [com.womai] **Pss Private Private SwapPss Heap Heap HeapTotal Dirty Clean Dirty Size Alloc Free------ ------ ------ ------ ------ ------ ------Native Heap 99280 99212 0 37 119040 107028 12011Dalvik Heap 7218 7200 0 42 16754 8377 8377Dalvik Other 1876 1876 0 0Stack 68 68 0 0Ashmem 6 4 0 0Other dev 12 0 12 0.so mmap 3362 416 1416 35.jar mmap 8 0 4 0.apk mmap 1979 0 264 0.ttf mmap 129 0 76 0.dex mmap 9934 16 6632 0.oat mmap 55 0 0 0.art mmap 8395 7436 208 0Other mmap 286 4 224 0GL mtrack 55744 55744 0 0Unknown 1647 1632 0 0TOTAL 190113 173608 8836 114 135794 115405 20388App SummaryPss(KB)------Java Heap: 14844Native Heap: 99212Code: 8824Stack: 68Graphics: 55744Private Other: 3752System: 7669TOTAL: 190113 TOTAL SWAP PSS: 114ObjectsViews: 295 ViewRootImpl: 3AppContexts: 6 Activities: 3Assets: 7 AssetManagers: 0Local Binders: 42 Proxy Binders: 46Parcel memory: 13 Parcel count: 57Death Recipients: 1 OpenSSL Sockets: 6WebViews: 0SQLMEMORY_USED: 156PAGECACHE_OVERFLOW: 19 MALLOC_SIZE: 124DATABASESpgsz dbsz Lookaside(b) cache Dbname4 28 62 6/18/5 /data/user/0/com.womai/databa
ses/google_analytics_v4.dbC:\Users\Administrator>
说明:可以通过页面关闭前后Views和Activities的数量来判断是否发生泄漏。
Memory Profiler
Memory Profiler是Android Studio提供的一个内存分析工具。(本文使用的是Android Studio 3.3.1)
Memory Profiler面板介绍:
1 用于强制执行垃圾回收Event的按钮。
2用户捕获堆转储的按钮。
3 用于记录内存分配情况的按钮。
4 用于放大/缩小时间线的按钮。
5用于跳转至实时内存数据的按钮。
6 Event时间线,其显示Activity状态、用户输入Event和屏幕旋转Event。
7 内存使用量时间线,其包含以下内容:
- 一个显示每个内存类别使用多少内存的堆叠图表,如左侧的y轴以及顶部的彩色健所示。
- 虚线表示分配的对象数,如右侧的y轴所示。
- 用于表示每个垃圾回收Event的图标。
Dump Java Heap
这个功能是用来获取当前应用的内存快照。通过分析内存快照,查看指定类的实例在内存中的情况,及其对象的引用关系,来判断内存是否泄漏。
NOTE: 在dump前,先点击一下GC按钮来强制内存回收一下,这样分析内存比较准确。
MAT
MAT (Memory Analyzer Tool)是一个快速且功能丰富的Java堆分析器,可以帮助您查找内存泄漏并减少内存消耗。
MAT下载地址:https://www.eclipse.org/mat/
Step1. 从AS的Memory Profiler中导出.hprof内存快照文件。
Step2. 转换.hprof文件。
AS导出的.hprof文件只能在AS的Memory Profiler中查看,要在MAT中查看,要使用hprof-conv进行转换。
hprof-conv工具的路径:<android_sdk>/paltform-tools/
转换命令:
hprof-conv heap-original.hprof heap-converted.hprof
Step3. 在MAT中打开转换好的.hprof文件。
Histogram:
Histogram是从类的角度进行分析,注重量的分析。
内存分析:
Step1. 查询指定的类。
Step2. 查询指定的对象被引用的地方。
Step3. 合并到GC Roots的最短路径。
说明:从上图可以看到MainActivity被sTest对象的context属性强引用,导致MainActivity泄漏。
Dominator Tree:
Dominator Tree是从对象实例的角度进行分析,注重引用关系分析。
LeakCanary
LeakCanary是Square开源的Android和Java的内存泄漏检测库。
LeakCanary地址:https://github.com/square/leakcanary
集成LeakCanary
在build.gradle中配置:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
在Application类中配置:
public class App extends Application {@Overridepublic void onCreate() {super.onCreate();if (LeakCanary.isInAnalyzerProcess(this)) {// This process is dedicated to LeakCanary for heap analysis.// You should not init your app in this process.return;}LeakCanary.install(this);// Normal app init code...}
}
使用:
内存泄漏代码:
// MainActivity.java
public class MainActivity extends AppCompatActivity {private static Test sTest;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sTest = new Test(this);}
}// Test.java
public class Test {private Context context;public Test(Context context) {this.context = context;}
}
运行应用,并退出首页,LeakCanary就会检测到MainActivity泄漏。
说明:从LeakCanary的检测结果可以看出,是因为MainActivity中的sTest对象的context属性持有MainActivity而导致其泄漏。
参考链接
循序渐进学用MAT排查Android Activity内存泄露
Android 内存泄露分析实战演练
Android 内存泄漏总结
微信 Android 终端内存优化实践
Android内存泄漏查找和解决
Leakcanary检测内存泄漏汇总
Java内存分配机制及内存泄漏
彻底搞懂Java内存泄露
使用 Memory Profiler 查看 Java 堆和内存分配
Android Studio和MAT结合使用来分析内存问题
Android内存申请分析
Android中导致内存泄漏的竟然是它----Dialog
记一次Activity的内存泄漏和分析过程
实践App内存优化:如何有序地做内存分析与优化
Android内存分析命令
JVM怎么判断对象是否已死?
欢迎爱学习的小伙伴加群一起进步:230274309 。 一起分享,一起进步!少划水,多晒干货!!欢迎大家!!!(进群潜水者勿加) |
Android内存泄漏分析相关推荐
- android释放acitity内存,Android 内存泄漏分析与解决方法
在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...
- Android内存泄漏分析及调试
2019独角兽企业重金招聘Python工程师标准>>> Android内存泄漏分析及调试 分类: Android2013-10-25 11:31 5290人阅读 评论(5) 收藏 举 ...
- Android 内存泄漏分析指北
android 内存泄漏分析指北 简单来说内存泄漏就是当对象不再被应用程序使用,但是垃圾回收器却不能移除它们,因为它们正在被引用 java 垃圾回收介绍: Java 虚拟机运行所管理的内存包括以下几个 ...
- Android 内存泄漏分析与解决方法
Android 内存泄漏分析与解决方法 参考文章: (1)Android 内存泄漏分析与解决方法 (2)https://www.cnblogs.com/start1225/p/6903419.html ...
- android 内存泄漏分析工具,Android内存泄漏终极解决篇(上)
一.概述 在Android的开发中,经常听到"内存泄漏"这个词."内存泄漏"就是一个对象已经不需要再使用了,但是因为其它的对象持有该对象的引用,导致它的内存不能 ...
- Mac Android 内存泄漏分析 实战演练
虚的概念就不讲了,自己去网上搜,一大堆. 这里来一次真刀真枪的实操实战演练. 简书上有一篇讲解 内存泄漏分析 的文章,总结的很到位,由浅入深,比较全面.建议结合起来阅读 内存泄露实例分析 -- An ...
- Android 内存泄漏分析(完)
什么是内存泄漏: 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗.内存泄漏并不是指物理上的内存消失,这里的内存泄漏是值由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内 ...
- Android内存泄漏分析及检测工具LeakCanary简介,androidui库
Android内存优化是APP稳定运行的重要一环,开发过程中如果代码写的过于随意,很容易造成内存泄漏,多次累积之后,便会产生OOM,进而造成app崩溃.本文介绍了内存泄漏的相关知识和检测工具LeakC ...
- android定时器内存泄露,Android内存泄漏分析以及解决方案
本文是看了公众号的文章,非常感谢,链接如下 概念 1.什么是内存泄漏? 一句话总结的话,那就是生命周期长的对象持有短生命周期的对象的引用导致其无法被及时释放,就会造成内存泄漏.(内存泄漏最终会导致内存 ...
最新文章
- jdk7 for Mac
- 用tar备份linux
- OpenCV拼接细节stitching detailed的实例(附完整代码)
- WebAPI(part8)--节点操作
- 动态规划 —— 背包问题 P05 —— 二维背包
- 输入一个数,判断这个数的二进制有几个0,几个1(完整代码)
- Magento Url重写修改
- 2728:摘花生(数字金子塔变形)
- android app功能测试,androidAPP功能测试要点幻灯片.pptx
- 数据结构 3优先队列(堆)
- select标签multiple属性的使用方法
- MySQL OCP备考
- 人脸识别门禁应用方案
- linux wipe命令,如何使用wipefs命令擦除磁盘上的签名
- strut2下载文件
- 九阴真经Ambari——3.安装并配置MariaDB
- UIWebView 使用
- 计算机无法连接网络显示错误651,电脑宽带连不上显示错误651是什么意思?
- eureka注册中心启动后访问,控制台出现socket read timeout exception
- 2046.重庆中巴飞机