此系列文章为本人对《Effective Java》一书的学习笔记,主要是记录对书中重点内容的理解。
既然有缘看到此文,那么希望能对你有所帮助。
本文对应原书第7条 消除过期的对象引用
Java自带垃圾回收功能,虽说解放了双手,但这样的自动化并不是万能的。
很多时候技术水平的不足会让我们写出带有隐形内存泄漏的bug,它可能在系统里沉睡数载,然后某一天突然发作,让你一脸懵逼,所以我们依然需要注意到内存管理的事情。
我们来看几个常见的内存泄漏来源:
来源一:过期引用
我们都直接到Java自带了一个栈
(Stack
)的实现类,类名就叫Stack
,有一天你学习了这个数据结构的原理,决定自己写一个,看看和官方的有啥区别,于是你掏出了1000+
的机械键盘,噼里啪啦操作了一波。
public class Stack {
// 底层使用的是数组 没毛病
private Object[] elements;
// size作为指针 没毛病
private int size = 0;
// 默认容量 没毛病
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
// 扩容机制
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
// 入栈
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
// 出栈 (注意这个方法!!!)
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
}
操作下来神清气爽,感觉自己基本无敌,的确,栈这个结构理解的是没有问题了,但这代码中包含了一个明显的内存泄漏风险,会让你最终一看战绩0 - 5
。
问题就处在出栈的pop
方法里:
当这个操作执行时,仅仅是做了指针size
的减一
,没有其它的处理,也就意味着,对于栈来说,其的元素变少了,但底层的数组却没发生改变,出栈元素的引用还在,是一个结结实实强引用。收垃圾的家伙根本没资格动人家。内存泄漏实锤!
怎么办呢,看看Java官方是怎么做的(并非官方源码,只是一个大概逻辑):
public Object pop() {
if(size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
elementData[size] = null; /* to let gc do its work */
return result;
}
你看了之后,冷笑了一下,因为你知道,你和Java的顶级大神的差距,只不过是一个 null
和一句注释:to let gc do its work
。
来源二:缓存
缓存用起来是真香,但同上述的内容一样,一旦你忘了它的存在,它就会慢慢占据你的内存。
怎么解决呢,举个例子,对于只要引用消失就失去存在价值的缓存,我们可以用WeakHashMap
来处理。
String key1 = new String("key1");
String key2 = new String("key2");
WeakHashMap map = new WeakHashMap();
map.put(key1, "缓存内容1");
map.put(key2, "缓存内容2");
我们有两个缓存内容,现在存入了WeakHashMap
,一旦业务上对于某个缓存不需要了,我们无需对WeakHashMap
进行主动操作,我们只需要将key1
置为null
。
key1 = null;
这样就将我们之前的new String("key1")
这个对象丢给了垃圾回收器,垃圾回收后WeakHashMap
的key1, "缓存内容1"
节点中的key就变为了null
。然后通过expungeStaleEntries
方法会将该节点清除。
来源三:监听器和其他回调
如果你实现了一个API,客户端在这个KPI中注册回调,但是没有显式的取消注册,那么在你没有对其作特殊处理的情况下,他们就会不断堆积,从而导致泄漏。解决方法和上面的例子一样,将它们保存为WeakHashMap
的键即可。
总结
内存泄漏的问题,有点类似狂犬病,一般情况下几个月内就会发病,也有潜伏多年的,但一旦发作,致死率几乎100%。所以,在写代码的时候就作好相应预防,十分重要。
水平有限,若文章中存在错误,恳请不吝赐教,这对我以及后面的读者都有重要意义。