一.什么是内存泄漏
通俗来说,定义了的变量没使用,就是内存泄漏了。Android虚拟机的垃圾回收采用的是根搜索算法,还一种是程序计数器算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。而内存泄漏出现的原因就是存在了无效的引用,导致本来需要被GC的对象没有被回收掉。
详情:Java内存分配&垃圾回收详解_jianning-wu的博客-CSDN博客
二.常见的内存泄露问题举例
1.单例导致内存泄露
代码
package com.example.myapplication;
import android.content.Context;
/**
* 不正确的单例模式导致内存泄露举例
* */
public class SingletonTest {
private static SingletonTest singletonTest;
private Context context;
private SingletonTest(Context context){
this.context=context;
}
public static SingletonTest getInstance(Context context){
if(null==singletonTest){
singletonTest=new SingletonTest(context);
}
return singletonTest;
}
}
分析
<1> 单例的含义是,其生命周期和Application一样。所以使用单例要格外小心,避免造成内存泄露。上述代码就很容易造成内存泄露。
<2> 上述代码中new SingletonTest对象的时候入参了一个Context对象,因为生命周期的问题,所以Context对象直到application销毁,Context对象才会被释放,如果该Context对象是一个Activity,就会导致该Activity该销毁的时候不能销毁导致内存泄露问题。
<3> 使用单例时,应该注意以下几点
(1) 尽量使用内部静态类初始化,这样既可以考虑到内存问题,也可以考虑到线程安全问题。
详见:常用的设计模式_android mvp mvvm_jianning-wu的博客-CSDN博客
(2) 使用单例时,入参Context或者Activity或者Fragment等等时,最好在具体方法是入参,而不是在构造方法中入参。这样避免入参的对象不能及时销毁。
(3) 使用单例,尽量避免单例持有大量数据。
2.匿名内部类导致内存泄漏
java代码
/**
* 匿名内部类导致内存泄露举例
* */
public class Test112233 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
int i = 9;
int j = 10;
int sum = i + j;
LoggerUtils.log("9+10=" + sum);
}
}).start();
}
}
字节码
Compiled from "Test112233.java"
public class XXX.XXX.com.XXX.main.Test112233 {
public XXX.XXX.com.XXX.main.Test112233();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/Thread
3: dup
4: new #3 // class XXX/XXX/com/XXX/main/Test112233$1
7: dup
8: invokespecial #4 // Method XXX/XXX/com/XXX/main/Test112233$1."<init>":()V
11: invokespecial #5 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
14: invokevirtual #6 // Method java/lang/Thread.start:()V
17: return
}
分析,main方法中
<1> code 4:new 一个匿名内部类的实例。
<2> code8:调用匿名内部类的构造方法。
这样一来生成的内部类就会持有外部类的引用,如果内部类异常,比如处理延迟的消息,不能及时销毁,就会导致外部类不能回收,将导致内存泄漏。
详见:Java查看字节码&内存分配&垃圾回收详解_jianning-wu的博客-CSDN博客
改成Lambda后的java代码
/**
* 匿名内部类导致内存泄露举例
* */
public class Test112233 {
public static void main(String[] args) {
new Thread(() -> {
int i = 9;
int j = 10;
int sum = i + j;
LoggerUtils.log("9+10=" + sum);
}).start();
}
}
改成Lambda后的字节码
Compiled from "Test112233.java"
public class XXX.XXX.com.XXX.main.Test112233 {
public XXX.XXX.com.XXX.main.Test112233();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/Thread
3: dup
4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: invokevirtual #5 // Method java/lang/Thread.start:()V
15: return
}
可见,在Lambda格式中,没有生成内部类,而是直接使用invokedynamic 指令动态调用run方法,从而避免了持有外部类的引用,也就避免了内存泄漏的发生。
3.Handler导致内存泄漏
详见:Handler详解(下)_jianning-wu的博客-CSDN博客
4.Bitmap导致内存泄漏
详见:Bitmap详解(上)_canvas: trying to use a recycled bitmap_jianning-wu的博客-CSDN博客
Bitmap详解(下)_jianning-wu的博客-CSDN博客
5.文件流等资源未关闭导致内存泄漏
public static Bitmap getBitmapByFile(File file) {
if (null == file) return null;
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(file));
return BitmapFactory.decodeStream(is);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} finally {
FileUtils.closeIO(is);
}
}
6.WebView导致内存泄漏
/**
* onDestroy方法
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (null != mWebView) {
mWebView.destroy();
mWebView = null;
}
}