上篇博客咱们写到了 Java/Android 内存的分配以及相关 GC 的详细分析,这篇博客咱们会继续分析 Android 中内存泄漏的检测以及相关案例,和 Android 的内存优化相关内容。
上篇:Android 性能优化以内存泄漏检测以及内存优化(上)。
中篇:Android 性能优化以内存泄漏检测以及内存优化(中)。
下篇:Android 性能优化以内存泄漏检测以及内存优化(下)。
转载请注明出处:blog.csdn.net/self_study/…
对技术感兴趣的同鞋加群544645972一块儿交流。javascript
经过上篇博客咱们了解了 Android JVM/ART 内存的相关知识和泄漏的缘由,再来归类一下内存泄漏的源头,这里咱们简单将其归为一下三类:html
内存泄漏不像闪退的 BUG,排查起来相对要困难一些,比较极端的状况是当你的应用 OOM 才发现存在内存泄漏问题,到了这种状况才去排查处理问题的话,对用户的影响就太大了,为此咱们应该在编码阶段尽早地发现问题,而不是拖到上线以后去影响用户体验,下面总结一下经常使用内存泄漏的定位和检测工具:java
Lint 是 Android studio 自带的静态代码分析工具,使用起来也很方便,选中须要扫描的 module,而后点击顶部菜单栏 Analyze -> Inspect Code ,选择须要扫描的地方便可:
android
StrictMode 是 Android 系统提供的 API,在开发环境下引入能够更早的暴露发现问题给开发者,于开发阶段解决它,StrictMode 最常被使用来检测在主线程中进行读写磁盘或者网络操做等耗时任务,把这些耗时任务放置于主线程会形成主线程阻塞卡顿甚至可能出现 ANR ,官方例子:git
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}复制代码
把上面这段代码放在早期初始化的 Application、Activity 或者其余应用组件的 onCreate 函数里面来启用 StrictMode 功能,通常 StrictMode 只是在测试环境下启用,到了线上环境就不要开启这个功能。启用 StrictMode 以后,在 logcat 过滤日志的地方加上 StrictMode 的过滤 tag,若是发现一堆红色告警的 log,说明可能就出现了内存泄漏或者其余的相关问题了:
github
LeakCanary 是一个 Android 内存泄漏检测的神器,正确使用能够大大减小内存泄漏和 OOM 问题,地址:web
https://github.com/square/leakcanary复制代码
集成 LeakCanary 也很简单,在 build.gradle 文件中加入:正则表达式
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}复制代码
而后在 Application 类中添加下面代码:shell
public class ExampleApplication extends Application {
@Override public 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...
}
}复制代码
上面两步作完以后就算是集成了 LeakCanary 了,很是简单方便,若是程序出现了内存泄漏会弹出 notification,点击这个 notification 就会进入到下面这个界面,或者集成 LeakCanary 以后在桌面会有一个 LeakCanary 的图标,点击进去是全部的内存泄漏列表,点击其中一项一样是进入到下面界面:
数据库
Memory Monitor 是 Android Studio 自带的一个监控内存使用状态的工具,入口以下所示:
Column | Description |
---|---|
Class Name | 占有这块内存的类名 |
Total Count | 未被处理的数量 |
Heap Count | 在上面选择的指定 heap 中的数量 |
Sizeof | 这个对象的大小,若是在变化中,就显示 0 |
Shallow Size | 在当前这个 heap 中的全部该对象的总数 |
Retained Size | 这个类的全部对象占有的总内存大小 |
Instance | 这个类的指定对象 |
Reference Tree | 指向这个选中对象的引用,还有指向这个引用的引用 |
Depth | 从 GC Root 到该对象的引用链路的最短步数 |
Shallow Size | 这个引用的大小 |
Dominating Size | 这个引用占有的内存大小 |
而后能够点击展开右侧的 Analyzer Tasks 项,勾选上须要检测的任务,而后系统就会给你分析出结果:
MAT(Memory Analyzer Tools)是一个 Eclipse 插件,它是一个快速、功能丰富的 JAVA heap 分析工具,它能够帮助咱们查找内存泄漏和减小内存消耗,MAT 插件的下载地址:Eclipse Memory Analyzer Open Source Project,上面经过 Android studio 生成的 .hprof 文件由于格式稍有不一样,因此须要通过一个简单的转换,而后就能够经过 MAT 去打开了:
能够经过命令 adb shell dumpsys meminfo [package name]
来将指定 package name 的内存信息打印出来,这种模式能够很是直观地看到 Activity 未释放致使的内存泄漏:
Android studio 还自带一个 Allocation Tracker 工具,功能和 DDMS 中的基本差很少,这个工具能够监控一段时间以内的内存分配:
咱们来看看常见的致使内存泄漏的案例:
因为静态变量的生命周期和应用同样长,因此若是静态变量持有 Activity 或者 Activity 中 View 对象的应用,就会致使该静态变量一直直接或者间接持有 Activity 的引用,致使该 Activity 没法释放,从而引起内存泄漏,不过须要注意的是在大多数这种状况下因为静态变量只是持有了一个 Activity 的引用,因此致使的结果只是一个 Activity 对象未能在退出以后释放,这种问题通常不会致使 OOM 问题,只能经过上面介绍过的几种工具在开发中去观察发现。
这种问题的解决思路很简单,就是不让静态变量直接或者间接持有 Activity 的强引用,能够将其修改成 soft reference 或者 weak reference 等等之类的,或者若是能够的话将 Activity Context 更换为 Application Context,这样就能保证生命周期一致不会致使内存泄漏的问题了。
咱们上面的 demo 中模拟的就是内部类对象持有外部类对象的引用致使外部类对象没法释放的问题,在 Java 中非静态内部类和匿名内部类会持有他们所属外部类对象的引用,若是这个非静态内部类对象或者匿名内部类对象被一个耗时的线程(或者其余 GC Root)直接或者间接的引用,甚至这些内部类对象自己就在作一些耗时操做,这样就会致使这个内部类对象直接或者间接没法释放,内部类对象没法释放,外部类的对象也就没法释放形成内存泄漏,并且若是没法释放的对象积累起来就会形成 OOM,示例代码以下所示:
public class SecondActivity extends AppCompatActivity{
private Handler handler;
private Bitmap bitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pic);//decode 一个大图来模拟内存没法释放致使的崩溃
findViewById(R.id.btn_second).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}).start();
}
}复制代码
这个问题的解决方法能够根据实际状况进行选择:
这个很好理解,在一个错误的地方使用 Activity Context,形成 Activity Context 被静态变量长时间引用致使没法释放而引起的内存泄漏,这个问题的处理方式也很简单,若是能够的话修改成 Application Context 或者将强引用变成其余引用。
资源性对象好比(Cursor,File 文件等)每每都用了一些缓冲,咱们在不使用的时候应该及时关闭它们,以便它们的缓冲对象被及时回收,这些缓冲不只存在于 java 虚拟机内,还存在于 java 虚拟机外,若是咱们仅仅是把它的引用设置为 null 而不关闭它们,每每会形成内存泄漏。可是有些资源性对象,好比 SQLiteCursor(在析构函数 finalize(),若是咱们没有关闭它,它本身会调 close() 关闭),若是咱们没有关闭它系统在回收它时也会关闭它,可是这样的效率过低了。所以对于资源性对象在不使用的时候,应该调用它的 close() 函数,将其关闭掉,而后再置为 null,在咱们的程序退出时必定要确保咱们的资源性对象已经关闭。
程序中常常会进行查询数据库的操做,可是常常会有使用完毕 Cursor 后没有关闭的状况,若是咱们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操做的状况下才会出现内存问题,这样就会给之后的测试和问题排查带来困难和风险,示例代码:
Cursor cursor = getContentResolver().query(uri...);
if (cursor.moveToNext()) {
... ...
}复制代码
更正代码:
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri...);
if (cursor != null && cursor.moveToNext()) {
... ...
}
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
//ignore this
}
}
}复制代码
在实际开发过程当中不免会有把对象添加到集合容器(好比 ArrayList)中的需求,若是在一个对象使用结束以后未将该对象从该容器中移除掉,就会形成该对象不能被正确回收,从而形成内存泄漏,解决办法固然就是在使用完以后将该对象从容器中移除。
具体的能够看看个人这篇博客:android WebView详解,常见漏洞详解和安全源码(下)。
一些 Android 程序可能引用咱们的 Android 程序的对象(好比注册机制),即便咱们的 Android 程序已经结束了,可是别的应用程序仍然还持有对咱们 Android 程序某个对象的引用,这样也会形成内存不能被回收,好比调用 registerReceiver 后未调用unregisterReceiver。假设咱们但愿在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息,则能够在 LockScreen 中定义一个 PhoneStateListener 的对象,同时将它注册到 TelephonyManager 服务中,对于 LockScreen 对象,当须要显示锁屏界面的时候就会建立一个 LockScreen 对象,而当锁屏界面消失的时候 LockScreen 对象就会被释放掉,可是若是在释放 LockScreen 对象的时候忘记取消咱们以前注册的 PhoneStateListener 对象,则会间接致使 LockScreen 没法被回收,若是不断的使锁屏界面显示和消失,则最终会因为大量的 LockScreen 对象没有办法被回收而引发 OOM,虽然有些系统程序自己好像是能够自动取消注册的(固然不及时),可是咱们仍是应该在程序结束时明确的取消注册。
还有一种状况是由于频繁的内存分配和释放,致使内存区域里面存在不少碎片,当这些碎片足够多,new 一个大对象的时候,全部的碎片中没有一个碎片足够大以分配给这个对象,可是全部的碎片空间加起来又是足够的时候,就会出现 OOM,并且这种 OOM 从某种意义上讲,是彻底可以避免的。
因为产生内存碎片的场景不少,从 Memory Monitor 来看,下面场景的内存抖动是很容易产生内存碎片的:
内存优化请看下篇:Android 性能优化以内存泄漏检测以及内存优化(下)。
blog.csdn.net/luoshengyan…
blog.csdn.net/luoshengyan…
blog.csdn.net/luoshengyan…
blog.csdn.net/luoshengyan…
blog.csdn.net/luoshengyan…
mp.weixin.qq.com/s?__biz=MzA…
geek.csdn.net/news/detail…
www.jianshu.com/p/216b03c22…
zhuanlan.zhihu.com/p/25213586
joyrun.github.io/2016/08/08/…
www.cnblogs.com/larack/p/60…
source.android.com/devices/tec…
blog.csdn.net/high2011/ar…
gityuan.com/2015/10/03/…
www.ayqy.net/blog/androi…
developer.android.com/studio/prof…
zhuanlan.zhihu.com/p/26043999