第六条 消除过期的对象引用
java与C++最大的不同或许就是在于java有自己的垃圾回收机制(对象的创建销毁都是由java虚拟机管理的),而C++所有对象的创建清理都是由程序员自己动手管理的。对java虚拟机方面的知识感兴趣的童鞋们可以去看看我写的另一系列文章《深入理解java虚拟机》学习笔记。
我们言归正传来看看为什么要消除过期的对像引用?如果我们不消除会有什么样的后果。其实答案显而易见——无非就是内存出了问题。内存问题一般也就两类吧,分别为:OOM(Out Of Memery)——内存溢出,Memery Leak——内存泄漏。所以我们这个算什么呢?没错,应该是内存泄漏。
这里讲一下什么叫做内存泄漏,内存溢出。
1. 内存溢出:简单讲就是你要使用的内存超过系统分给你的内存大小。很简单的一个程序就可以导致内存溢出;
void a()
{
a();
}
调用这个方法,必然出异常——无限递归自身导致虚拟机栈深度无限增长超出虚拟机本身预留的栈大小。大家尝试的时候可要小心哦~~大概率电脑死机!!
2. 内存泄漏:就是你用了一块内存以后没有回收,相当于总的内存少了一块,这就是泄漏的直观意思。java是支持垃圾回收的语言,在方便的同时,一旦发生内存泄漏,就很难找出到底是哪里出了原因。
下面举一个书中的例子。是关于栈在伸缩过程中出现的内存泄漏的。
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop()
{
if (size==0) {
throw new EmptyStackException();
}
return elements[--size];
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
模拟的是一个栈的数据结构。乍一看这个程序完美的一批,怎么测试都不会出问题。但其实在栈先增长后收缩的过程中,出现了内存泄漏。因为垃圾收集器并没有在栈收缩后把栈外的对象回收掉——原因是虚拟机此时还维护着这个对象的引用,那么垃圾回收器在确认哪些对象应该被回收的时候就会跳过仍然维护引用的对象。而且这时候更恐怖的事情是,你永远也解除不掉这个对象的引用。
解决的方法也很简单,只要在pop的时候把对象置为null就行。
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = elements[size - 1];
elements[size - 1] = null;
return result;
}
这样子操作之后,超过栈范围的已分配的对象没有有效的引用,在垃圾回收器工作时就能有效的清理无用的空间,避免的内存泄漏。
另外的书中还提到其他的东西。我在这里写一下:
1.消除过期引用最好的方法是让饱含其引用的变量结束其生命周期。如上例中结束Stack对象的生命周期,Stack对象被垃圾回收时,必然其类里面的属性都会被回收。
2.当类自己管理内存时,我们就更应该警惕内存泄漏问题。因为内存泄漏问题不会表现出明显的失败,所以这个问题可能会在系统中存在许多年。
3.内存泄漏的另外两个来源是缓存,监听器和其他回调。放在缓存中忘了清除,注册了监听器 忘了取消注册都会在一定程度上导致内存泄漏。