JVM之内存泄漏的详细解析

内存泄漏

泄露溢出

内存泄漏(Memory Leak):是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。由于代码的实现不同就会出现很多种内存泄漏问题,让 JVM 误以为此对象还在引用中,无法回收,造成内存泄漏

内存溢出(out of memory)指的是申请内存时,没有足够的内存可以使用

内存泄漏和内存溢出的关系:内存泄漏的越来越多,最终会导致内存溢出


几种情况
静态集合

静态集合类的生命周期与 JVM 程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。原因是长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收

复制代码
public class MemoryLeak {
    static List list = new ArrayList();
    public void oomTest(){
        Object obj = new Object();//局部变量
        list.add(obj);
    }
}

单例模式

单例模式和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和 JVM 的生命周期一样长,所以如果单例对象持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏


内部类

内部类持有外部类的情况,如果一个外部类的实例对象调用方法返回了一个内部类的实例对象,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象也不会被回收,造成内存泄漏


连接相关

数据库连接、网络连接和 IO 连接等,当不再使用时,需要显式调用 close 方法来释放与连接,垃圾回收器才会回收对应的对象,否则将会造成大量的对象无法被回收,从而引起内存泄漏


不合理域

变量不合理的作用域,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏;如果没有及时地把对象设置为 null,也有可能导致内存泄漏的发生

复制代码
public class UsingRandom {
    private String msg;
    public void receiveMsg(){
        msg = readFromNet();// 从网络中接受数据保存到 msg 中
        saveDB(msg);        // 把 msg 保存到数据库中
    }
}

通过 readFromNet 方法把接收消息保存在 msg 中,然后调用 saveDB 方法把内容保存到数据库中,此时 msg 已经可以被回收,但是 msg 的生命周期与对象的生命周期相同,造成 msg 不能回收,产生内存泄漏

解决:

  • msg 变量可以放在 receiveMsg 方法内部,当方法使用完,msg 的生命周期也就结束,就可以被回收了

  • 在使用完 msg 后,把 msg 设置为 null,这样垃圾回收器也会回收 msg 的内存空间。


改变哈希

当一个对象被存储进 HashSet 集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进 HashSet 集合中时的哈希值不同,这种情况下使用该对象的当前引用作为的参数去 HashSet 集合中检索对象返回 false,导致无法从 HashSet 集合中单独删除当前对象,造成内存泄漏


缓存泄露

内存泄漏的一个常见来源是缓存,一旦把对象引用放入到缓存中,就会很容易被遗忘

使用 WeakHashMap 代表缓存,当除了自身有对 key 的引用外没有其他引用,map 会自动丢弃此值


案例分析
复制代码
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 函数存在内存泄漏问题,因为 pop 函数只是把栈顶索引下移一位,并没有把上一个出栈索引处的引用置空,导致栈数组一直强引用着已经出栈的对象

解决方法:

复制代码
public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;
    return result;
}

相关推荐
左灯右行的爱情5 小时前
深入理解 G1 GC:已记忆集合(RSet)与收集集合(CSet)详解
java·jvm·后端·juc
智商低情商凑19 小时前
CAS(Compare And Swap)
java·jvm·面试
Mr__Miss21 小时前
JVM学习笔记
jvm·笔记·学习
碎梦归途1 天前
23种设计模式-结构型模式之适配器模式(Java版本)
java·开发语言·jvm·单例模式·设计模式·适配器模式
江沉晚呤时1 天前
深入了解递归、堆与栈:C#中的内存管理与函数调用
java·jvm·算法
银河麒麟操作系统1 天前
【银河麒麟高级服务器操作系统】磁盘只读问题分析
java·linux·运维·服务器·jvm
爱的叹息1 天前
Java虚拟机(JVM)家族发展史及版本对比
java·开发语言·jvm
左灯右行的爱情1 天前
深度学习与总结JVM专辑(七):垃圾回收器—CMS(图文+代码)
jvm
碎梦归途2 天前
23种设计模式-结构型模式之享元模式(Java版本)
java·开发语言·jvm·设计模式·享元模式
左灯右行的爱情2 天前
JVM-卡表
java·jvm·算法