内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃(内存溢出OOM)等严重后果。
内存泄露的危害:
Android虚拟机有垃圾回收机制,会回收掉大部分的内存空间,但是有一些逻辑上已经不再使用的对象,被GC Root直接或间接引用,导致垃圾回收器不能回收它们。
Jvm(Java虚拟机)主要管理两种类型内存:堆和非堆。 堆是运行时数据区域,所有类实例和数组的内存均从此处分配。 非堆是JVM留给自己用的,包含方法区、虚拟机栈、本地方法栈、程序计数器。
对于绝大多数应用来说,堆内存是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。堆内存由垃圾回收器的自动内存管理系统回收,出现内存泄漏也主要在这个区域
可达性分析算法:从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。 即使循环引用了,只要没有被GC Roots引用了依然会被回收。 但是,这个GC Roots的定义就要考究了,Java语言定义了如下GC Roots对象:
虚拟机栈(帧栈中的本地变量表)中引用的对象。 方法区中静态属性引用的对象。 方法区中常量引用的对象。 本地方法栈中JNI引用的对象。
通过集成 LeakCanary ,一旦检测到内存泄漏,LeakCanary 就会 dump Memory 信息,并通过另一个进程分析内存泄漏的信息并展示出来,随时发现和定位内存泄漏问题,极大地方便了Android应用程序的开发。
LeakCanary 是通过在 Application 的 registerActivityLifecycleCallbacks 方法实现对 Activity 销毁监听的,在 Activity 在销毁时,将 Activity 包装到 WeakReference 中,被 WeakReference 包装过的 Activity 对象如果能够被回收,则说明引用可达,垃圾回收器就会将该 WeakReference 引用存放到 ReferenceQueue 中。相反就可能发生了内存泄漏。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}
通过过滤 Logcat 中的 LeakCanary 标签来确认 LeakCanary 在启动时正在运行:
D/LeakCanary: LeakCanary is running and ready to detect memory leaks.
在 LeakCanary2.0 之前我们接入的时候需要在 Application.onCreate 方法中显式调用 LeakCanary.install(this); 开启 LeakCanary 的内存监控。
从 LeakCanary2.0 开始通过库里注册的 ContentProvier 自己开启 LeakCanary 的内存监控,无需用户手动再添加初始化代码。
安装测试应用后,手机上会出现如下两个图标:左侧的是测试应用的图标,右侧是自动安装的 LeakCanary 的图标。
打开可能存在内存泄漏页面,返回退出页面。等待大概10秒,LeakCanary 就会检测到,并进行分析。在通知栏可以看到进度。
分析完成后,然后在通知栏会收到通知。
点开通知,会打开 LeakCanary 的界面
另外,在 Logcat 中也可以看到 LeakCanary 的相关信息。LeakCanary 将应用中发现的泄漏分为两类:应用程序泄漏和库泄漏。
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
......
====================================
0 LIBRARY LEAK
————————————————
android studio自带的Profiler MEMORY工具,通过Capture heap dump抓取堆内存快照分析内存泄漏。
如存在泄漏,会提示相应泄漏对象:
根据泄漏对象references查看对象在哪里被引用,一般看是否被长生命周期对象(单例、静态对象、handler等)引用导致无法回收
单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。
常见的有:
public class UpdateManager {
private static volatile UpdateManager mInstance;
private Context mContext;
private UpdateManager(Context context) {
mContext = context;
}
public static UpdateManager getInstance(Context context) {
if (mInstance == null) {
synchronized (UpdateManager.class) {
if (mInstance == null) {
mInstance = new UpdateManager(context);
}
}
}
return mInstance;
}
}
public class StaticClass {
// 定义1个静态变量
private Static Context mContext;
//...
// 引用的是Activity或Service的context
mContext = context;
// 当Activity或Service需销毁时,由于mContext = 静态 & 生命周期 = 应用程序的生命周期,故 Activity无法被回收,从而出现内存泄露
}
正例:
public class UpdateManager {
//...省略代码
private UpdateManager(Context context) {
mContext = context.getApplicationContext();
}
//...省略代码
}
public class UpdateManager {
public void addUpdateListener(UpdateListener listener) {
if (mUpdateListener.contains(listener)) {
return;
}
mUpdateListener.add(listener);
}
}
正例:
public class UpdateManager {
public void addUpdateListener(UpdateListener listener) {
if (mUpdateListener.contains(listener)) {
return;
}
mUpdateListener.add(listener);
}
public void removeUpdateListener(UpdateListener listener) {
mUpdateListener.remove(listener);
}
}
非静态类和匿名类默认会持有外部类的引用,所以在长生命周期的类中引用了非静态内部类、匿名类就会导致内存泄漏。常见的解决方式是静态内部类+WeakReference
public class TestActivity extends Activity {
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
showContent();
}
}
};
//使用mHandler.sendEmptyMessageDelayed(5000,1)发送消息,并未清理
}
正例一:在适当的位置情况已发送的Message
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
正例二:使用静态内部类+WeakReference
private MyHandler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new MyHandler(this);
}
private static class MyHandler extends Handler {
private final WeakReference<TestActivity> mWeakActivity;
public MyHandler(TestActivity testActivity) {
super();
mWeakActivity = new WeakReference<>(testActivity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
TestActivity testActivity = mWeakActivity.get();
if (testActivity == null) {
return;
}
if (msg.what == 1) {
testActivity.showContent();
}
}
}
public class TestActivity extends Activity {
private void execute() {
new Thread() {
@Override
public void run() {
doSomething();
}
}.start();
}
private void doSomething(){
//Do time-consuming operations
}
}
正例:同样可以使用静态内部类+WeakReference处理,但一般不直接new Thread进行异步操作,而是使用线程池,最好是用RxJava、kotlin协程,这样可以更好管理线程,并且可以在组件销毁时停止异步操作
public class TestActivity extends Activity {
private static class MyThread extends Thread {
private final WeakReference<TestActivity> mWeakActivity;
public MyThread(TestActivity activity) {
mWeakActivity = new WeakReference<>(activity);
}
@Override
public void run() {
super.run();
if (mWeakActivity.get() == null) {
return;
}
doSomething();
}
private void execute() {
new MyThread(this).start();
}
}
反例:未释放资源
public static void writeFile(File file, String content) {
try {
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8));
bw.write(content);
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
正例:在finally中close
public static void writeFile(File file, String content) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8));
bw.write(content);
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Android中有很多服务,比如PowerManager,AlarmManager,NotificationManager等,通常使用起来也很方便,就是使用Context.getSystemService方法来获得。但部分Manager会持有调用的Context,例如:PowerManager
public PowerManager(Context context, IPowerManager service, IThermalService thermalService,
Handler handler) {
mContext = context;
mService = service;
mThermalService = thermalService;
mHandler = handler;
}
所以如果获取PowerManager的时候使用的是Activity或Service的Context,并且在单例或static类中保存了PowerManager,就会导致内存泄漏。泄漏分析过程可参考:https://cloud.tencent.com/developer/article/1328418
解决方案:
//ContextThemeWrapper也优先处理了LayoutManager服务
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
a. 如果服务和UI相关,则用Activity
b. 如果是类似ALARM_SERVICE,CONNECTIVITY_SERVICE建议有限选用Application Context
c. 如果出现出现了内存泄漏,排除问题,可以考虑使用Application Context
无论是系统广播还是本地广播,注册与反注册需成对出现