一.定义
首先什么是内存泄漏,简单点说就是用完了忘了回收,而其他对象等资源想用却没法用的一种“站着茅坑不拉屎”的浪费资源的情况。在C/C++中,多数泄漏的场景就是程序离开某一运行域时,如在某个方法体中new出的对象或者malloc出的结构体等,并且只有该方法体中的局部变量指向这块内存区域时,在该方法返回时,存在栈中的局部变量指针随着栈帧被一起销毁,那么就没有任何指针可以指向该内存区域了,那么这块内存区域便是泄漏了。
而java的内存泄漏呢?众所周知,java的内存回收是由gc管理的。gc运行的是可达性算法,jvm中的gc线程将java对象从一个特定的对象gcroot开始,看成一个树,遍历树以判断对象的可达性,和C++那种静态的指针计数拥有本质区别,不存在无用对象间相互引用造成的困扰。也就是说当java对象真正确实没有任何一个来自存活对象的(强)引用(弱引用等除外)的时候(如果如果引用来自不可达对象,那么该对象仍然是不可达的),java对象才可能会被回收。也就是说,这里的java内存泄漏和传统的C/C++是不尽相同的。
下图是java的可达性算法:
二.什么是java的内存泄漏
# instance fields
.field final synthetic this$0:Lcom/gy/just/VoltageMonitor/View/Activity/YunWeiActivity;
.field final synthetic val$sec:I
# direct methods
.method constructor <init>(Lcom/gy/just/VoltageMonitor/View/Activity/YunWeiActivity;I)V
.locals 0
.param p1, "this$0" # Lcom/gy/just/VoltageMonitor/View/Activity/YunWeiActivity;
编译器自动给内部类加了一个叫“this$0”的外部类变量,并且在构造函数中赋值。那么内部类中就持有有了一个不可见的外部类强引用。像Runnable,Handler这类的内部类如果子线程没有结束的话,那么外部类对象的泄漏就容易发生了。public class B extends A{
public void test(){
new Thread(new Demo()).start();
}
@Override
public void dosth() {
// TODO Auto-generated method stub
System.out.println("doB");
}
class Demo implements Runnable{
public Demo(){
try {
Field field = getClass().getDeclaredField("this$0");
field.setAccessible(true);
field.set(this, null);
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
dosth();
}
}
}
运行异常如下public abstract interface java.util.List<E>
Exception in thread "Thread-0" java.lang.NullPointerException
at test.B$Demo.run(B.java:46)
at java.lang.Thread.run(Thread.java:745)
怎么样?内部类Demo已经无法调用外部类的dosth方法了,抛出空指针异常。