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;
}

相关推荐
cur1es10 分钟前
【JVM类加载&双亲委派模型&垃圾回收机制】
java·jvm·gc·垃圾回收·类加载·双亲委派模型
Mr.朱鹏15 分钟前
JVM-GC垃圾回收案例
java·jvm·spring boot·算法·spring·spring cloud·java-ee
老友@1 小时前
JVM 底层彻底理解
jvm
java1234_小锋1 小时前
Python常见面试题:Python是如何进行内存管理的?
java·jvm·python
Coder_Boy_2 小时前
从Java虚拟机到分布式中间件:高并发体系全解析(含电商实践细节)
java·jvm·分布式·spring·中间件
6+h2 小时前
【Java】JDK、JRE、JVM三者最通俗的讲解
java·jvm·python
小年糕是糕手3 小时前
【35天从0开始备战蓝桥杯 -- Day2】
开发语言·jvm·数据库·c++·程序人生·考研·蓝桥杯
小年糕是糕手3 小时前
【35天从0开始备战蓝桥杯 -- Day1】
jvm·数据结构·c++·程序人生·算法·职场和发展·蓝桥杯
Derek_Smart9 天前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
大道至简Edward10 天前
深入 JVM 核心:一文读懂 Class 文件结构(附 Hex 实战解析)
jvm