性能优化系列阅读
为什么内存优化?
在一个商业项目中,很有可能因为工程师的疏忽,导致代码质量不佳,影响到程序的运行效率,从而让用户感知到应用的卡顿、崩溃。而Android开发中,每个Android应用在手机上申请的内存空间都是有限的。虽然手机发展越来越快,可申请到的内存越来越大,但是也不能大手大脚,随便浪费应用可使用的内存空间。内存一旦不够时,你这个应用就会因为OOM(out of memory)而崩溃。因此,内存优化这一块内容,在开发应用时是非常重要的。
1. 内存优化的关键点—避免内存泄露
内存优化中非常关键的一点,就是避免内存泄露。因为内存泄露会严重的导致内存浪费,所以避免内存泄露,是内存优化中必不可少的。
2. java中的四种引用类型
java引用类型不是指像int、char等这些基本的数据类型。java中的引用类型有四种:强引用、软引用、弱引用、虚引用。这四种引用类型,它们关于对象的可及性是由强到弱的。
public class ReferenceDemo {
public static void main(String[] args) {
// 强引用:对象类型 对象的名字(实例) = 对象的构造方法;
String str = "abc"; // 常量池
// String str = new String("abc"); // 堆内存
// 软引用,当内存不足的时候,才会释放掉它引用的对象
SoftReference<String> softReference = new SoftReference<String>(str);
// 弱引用,只要系统产生了GC(垃圾回收),它引用的对象就会被释放掉
WeakReference<String> weakReference = new WeakReference<String>(str);
// 虚引用,实际用的不多,就是判断对象已被回收
// PhantomReference<String> phantomReference = new PhantomReference<String>(referent,q);
str = null;
System.out.println("强引用:" + str);
softReference.clear();
System.out.println("软引用:" + softReference.get());
// 通过GC,将String对象回收了,那你引用中的对象也会变成null,gc只回收堆内存
System.gc();
System.out.println("弱引用:" + weakReference.get());
}
}
2.1 强引用
最常见的强引用方式如下:
//强引用 对象类型 对象名 = new 对象构造方法();
//比如下列代码
String str = new String("abc");
在上述代码中,这个str对象就是强可及对象。强可及对象永远不会被GC回收。它宁愿被抛出OOM异常,也不会回收掉强可及对象。
清除强引用对象中的引用链如下:
String str = new String("abc");
//置空
str = null;
2.2 软应用
软引用方式如下:
//软引用SoftReference
SoftReference<String> softReference = new SoftReference<String>(str);
在上述代码中,这个str对象就是软可及对象。当系统内存不足时,软可及对象会被GC回收。
清除软引用对象中的引用链可以通过模拟系统内存不足来清除,也可以手动清除,手动清除如下:
SoftReference<String> softReference = new SoftReference<String>(str);
softReference.clear();
2.3 弱引用
弱引用方式如下:
//弱引用WeakReference
WeakReference<String> weakReference = new WeakReference<>(str);
在上述代码中,这个str对象就是弱可及对象。当每次GC时,弱可及对象就会被回收。
清除弱引用对象中的引用链可以通过手动调用gc代码来清除,如下:
WeakReference<String> weakReference = new WeakReference<>(str);
System.gc();
当然,也可以通过类似软引用,调用clear()方法也可以。
2.4 虚引用
虚引用方式如下:
//虚引用PhantomReference
PhantomReference phantomReference = new PhantomReference<>(arg0, arg1);
虚引用一般在代码中出现的频率极低,主要目的是为了检测对象是否已经被系统回收。它在一些用来检测内存是否泄漏的开源项目中使用到过,如LeakCanary。
2.5 补充
- 一个对象的可及性由最强的那个来决定。
- System.gc()方法只会回收堆内存中存放的对象。
String str = "abc";
//弱引用WeakReference
WeakReference<String> weakReference = new WeakReference<>(str);
System.gc();
像这样的代码,即使gc后,str对象仍然可以通过弱引用拿到。因为像”abc”这种,并没有存放在堆内 存中,它被存放在常量池里,所以gc不会去回收。
3. 内存泄露的原因
对无用对象的引用一直未被释放,就会导致内存泄露。如果对象已经用不到了,但是因为疏忽,导致代码中对该无用对象的引用一直没有被清除掉,就会造成内存泄露。
比如你按back键关掉了一个Activity,那么这个Activity页面就暂时没用了。但是某个后台任务如果一直持有着对该Activity对象的引用,这个时候就会导致内存泄露。
4. 检测内存泄露—LeakCanary
在全球最大的同性交友网站github中,有一个非常流行的开源项目LeakCanary,它能很方便的检测到当前开发的java项目中是否存在内存泄露。
5. LeakCanary的使用
5.1 官方使用文档描述
从LeakCanary的文档描述中,可以得知使用方式,简单翻译为如下步骤:
1.在你的项目中,找到moudle级别的build.gradle文件,并在dependencies标签里加上以下代码:
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'
}
2.在你Android项目中,找到先前写的Application类(PS:如果没有,那么请自行新建并在AndroidManifest中声明),并添加如下代码:
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...
}
}
3.导入完毕!当你的应用出现内存泄露时,LeakCanary会在通知栏上进行通知,注意查看。下图是一个LeakCanary检测到内存泄露时的实示例。
5.2 检测Fragment
上述步骤默认会检测Activity,但是不会去检测Fragment,如果需要对某个Fragment检测的话,需要利用到LeakCanary的其他写法。
首先,在先前的Application类中,改写为以下代码:
public class MyApplication extends Application {
public static RefWatcher mRefWatcher;
@Override public void onCreate() {
super.onCreate();
//...
mRefWatcher = LeakCanary.install(this);
// Normal app init code...
}
}
然后在Fragment中的onDestroy方法中,去使用这个静态的RefWatcher进行观察,如果onDestroy了当前这个Fragment还没被回收,说明该Fragment产生了内存泄露。
@Override
public void onDestroy() {
super.onDestroy();
MyApplication.mRefWatcher.watch(this);
}
5.3 检测某个特定对象
有时候如果需要检测某个特定的可疑对象在某个时机下是否内存泄露,那么只需要执行如下代码
(假如对象名为someObjNeedGced):
//...
RefWatcher refWatcher = MyApplication.refWatcher;
refWatcher.watch(someObjNeedGced);
//...
当执行了refWatcher.watch方法时,如果这个对象还在内存中被其他对象引用,就会在 logcat 里看到内存泄漏的提示。
6. LeakCanary的原理简介
LeakCanary的代码执行流程图如下:
LeakCanary 的机制如下:
RefWatcher.watch()
会以监控对象来创建一个KeyedWeakReference
弱引用对象在
AndroidWatchExecutor
的后台线程里,来检查弱引用已经被清除了,如果没被清除,则执行一次 GC如果弱引用对象仍然没有被清除,说明内存泄漏了,系统就导出 hprof 文件,保存在 app 的文件系统目录下
HeapAnalyzerService
启动一个单独的进程,使用HeapAnalyzer
来分析 hprof 文件。它使用另外一个开源库 HAHA。HeapAnalyzer
通过查找KeyedWeakReference
弱引用对象来查找内在泄漏HeapAnalyzer
计算KeyedWeakReference
所引用对象的最短强引用路径,来分析内存泄漏,并且构建出对象引用链出来。内存泄漏信息送回给
DisplayLeakService
,它是运行在 app 进程里的一个服务。然后在设备通知栏显示内存泄漏信息。