Effective Java -- 常见内存泄漏的来源

此系列文章为本人对《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")这个对象丢给了垃圾回收器,垃圾回收后WeakHashMapkey1, "缓存内容1"节点中的key就变为了null。然后通过expungeStaleEntries方法会将该节点清除。

来源三:监听器和其他回调

如果你实现了一个API,客户端在这个KPI中注册回调,但是没有显式的取消注册,那么在你没有对其作特殊处理的情况下,他们就会不断堆积,从而导致泄漏。解决方法和上面的例子一样,将它们保存为WeakHashMap的键即可。

总结

内存泄漏的问题,有点类似狂犬病,一般情况下几个月内就会发病,也有潜伏多年的,但一旦发作,致死率几乎100%。所以,在写代码的时候就作好相应预防,十分重要。

水平有限,若文章中存在错误,恳请不吝赐教,这对我以及后面的读者都有重要意义。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值