Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java 常见内存泄露的原因及解决

Java 常见内存泄露的原因及解决

作者头像
用户3147702
发布于 2022-06-27 04:28:36
发布于 2022-06-27 04:28:36
1.9K00
代码可运行
举报
运行总次数:0
代码可运行

1. 概述

java 语言的一个重要的特性就是垃圾收集器的自动收集和回收,而不需要我们手动去管理和释放内存,这也让 java 内存泄漏问题更加难以发现和处理。

如果你的程序抛出了 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space,那么通常这就是因为内存泄露引起的。

2. 什么是内存泄露

总的来说,释放对象的原则就是他再也不会被使用,给一个对象赋值为 null 或者其他对象,就会让这个对象原来所指向的空间变得无法访问,也就再也无法被使用从而等待 GC 的回收。 内存泄露指的就是虽然这部分对象的内存已经不会再被使用,但是他们却不会被 jvm 回收。

  • 通常,如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露

3. 作用域过大造成的内存泄露

3.1. 问题描述

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Simple {
    private Object object;
    public void method() {
        object = new Object();
        // ...
    }
}

以上的代码中,我们在 method 方法中为类成员变量 object 赋值了实例化后的值,但是如果我们仅仅在这个方法中使用到了 object,那将意味着在整个类的生命周期中,object 所占用的空间虽然都不会被再次使用,但却始终无法得以回收,这就造成了内存泄露,如果 object 是一个加入了很多元素的容器,则问题将暴露的更加明显。

3.2. 改进

上述内存泄露代码的改进比较简单。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Simple {
    private Object object;
    public void method() {
        object = new Object();
        // 使用到 object 的业务代码
        object = null;
    }
}

解决内存泄露问题的原则就是在对象不再被使用的时候立即释放相应的引用,因此在业务代码执行后,object 对象不再使用时,赋值为 null,释放他的引用就可以让 jvm 回收相应的内存了。

下面是一段 jdk8 LinkedList 的源码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//删除指定节点并返回被删除的元素值
E unlink(Node<E> x) {
    //获取当前值和前后节点
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    if (prev == null) {
        //如果前一个节点为空(如当前节点为首节点),后一个节点成为新的首节点
        first = next;
    } else {
        //如果前一个节点不为空,那么他先后指向当前的下一个节点
        prev.next = next;
        x.prev = null;
    }
    if (next == null) {
        //如果后一个节点为空(如当前节点为尾节点),当前节点前一个成为新的尾节点
        last = prev;
    } else {
        //如果后一个节点不为空,后一个节点向前指向当前的前一个节点
        next.prev = prev;
        x.next = null;
    }
    x.item = null;
    size--;
    modCount++;
    return element;
}

可以看到,在对 x 的成员 next、item、prev 的使用结束后,都显式赋值了 null,以免他们无法被 jvm 回收,在实际开发中,很容易被忽略。

4. 容器元素造成的内存邪路

4.1. 问题描述

下面是我们通过 ArrayList 实现的一个 pop 方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public E pop(){
    if(size == 0)
        return null;
    else
        return (E) elementData[--size];
}

实现起来非常简单,但是却存在着内存泄露的问题,因为 size 变小导致 ArrayList 中原有的末端元素将永远得不到使用,但是由于容器持有着他们的引用,他们也永远得不到释放。

4.2. 改进

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public E pop(){
    if(size == 0)
        return null;
    else{
        E e = (E) elementData[--size];
        elementData[size] = null;
        return e;
    }
}

通过主动赋值为 null 从而释放相应元素的引用,从而让相应的空间得以回收。

5. 容器本身造成的内存泄露

5.1. 问题描述

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Vector vec = new Vector();
for (int i = 1; i < 100; i++)
{
    Object obj = new Object();
    vec.add(obj);
    // 使用 obj 的相关业务逻辑
    obj = null;
}
// 使用 vec 的相关业务逻辑

上面的代码是一个非常经典的例子,乍看之下没有任何问题,每次使用元素后,将元素引用置为 null,保证了 object 空间的回收。 但是,事实上,容器本身随着不断的扩容,也占用着非常大的内存,这是常常被忽略的,如果不将容器本身赋值为 null,则容器本身会在作用域内一直存活。

5.2. 改进

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Vector vec = new Vector();
for (int i = 1; i < 100; i++)
{
    Object obj = new Object();
    vec.add(obj);
    // 使用 obj 的相关业务逻辑
    obj = null;
}
// 使用 vec 的相关业务逻辑
vec = null;

改进方法也很简单,在不再使用容器的时候立即赋值为 null 总是最正确的。

6. Set、Map 容器使用默认 equals 方法造成的内存泄露

6.1. 问题描述

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class TestClass implements Cloneable {
    private Long value;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class MainClass {
    public Set<TestClass> method(List<TestClass> testList)
        throws CloneNotSupportedException {
        Set<TestClass> result = new HashSet<>();
        for (int a = 0; a < 100000) {
            for (TestClass test : testList) {
                result.add(test.clone());
            }
        }
    }
}

看上去,上述代码实现了对传入的 testList 去重的代码逻辑,虽然重复了很多很多次,但我们的去重代码并不会造成额外的空间浪费。 但是事实上,clone、new 操作都是重新在内存中分配空间,这也就意味着他们的地址是不同的,而所有的类由于都继承了 Object,所以他们的 equals 方法都来源于 Object 类,默认的实现是返回对象地址。 因此,虽然是 clone 得到的对象在 Set 中去重,但是 Set 还是认为他们是不同的对象,从而反复添加造成最终抛出 OutOfMemoryError。

6.2. 改进

改进方式很简单,对于自定义的类,添加所需的适当 equals 方法的实现即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class TestClass implements Cloneable {
    private Long value;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public boolean equals(Object obj) {
        return Objects.equals(obj.value, value);
    }
}

public class MainClass {
    public Set<TestClass> method(List<TestClass> testList)
        throws CloneNotSupportedException {
        Set<TestClass> result = new HashSet<>();
        for (int a = 0; a < 100000) {
            for (TestClass test : testList) {
                result.add(test.clone());
            }
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
猿思考系列9——一文获取隐藏逻辑挖掘办法
看完上一个章节,相信你已经充分的掌握了数据库事务的一些事情,猿人工厂君也知道,内容对于新手而言,理解起来还是比较很吃力的,文中提到的原理和内容,有兴趣的可以和我一起探讨,猿人工厂君就不一一赘述了。之前有小伙伴反应,数据结构的知识比较薄弱,今天我们就来探讨下,通过思考的方式,来解决基本的数据结构问题。本文是思考系列的最后一章,从下一章节开始,带你玩儿框架和实战完成猿蜕变!
山旮旯的胖子
2020/07/28
2840
猿思考系列9——一文获取隐藏逻辑挖掘办法
一文打通java中内存泄露
可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。
一个风轻云淡
2023/10/15
5290
一文打通java中内存泄露
JDK源码阅读:ArrayList原理
查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。
鳄鱼儿
2024/05/21
1580
Java之ArrayList解剖学
回归基础,回归原理,你会有更深的领悟。今天来聊一聊在Java当中常用的一个集合类:ArrayList。
23号杂货铺
2019/09/27
4200
Java之ArrayList解剖学
项目中的全局缓存导致了内存泄露?
每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过“引用”。在 Java 中一切都被视为了对象,但是我们操作的标识符实际上是对象的一个引用(reference)。
架构探险之道
2020/09/18
7650
引起Java中内存泄露的8种场景归纳,建议收藏
如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
终码一生
2022/04/14
1.8K0
引起Java中内存泄露的8种场景归纳,建议收藏
面试官:兄弟,说说 ArrayList 和 LinkedList 有什么区别
ArrayList 和 LinkedList 有什么区别,是面试官非常喜欢问的一个问题。可能大部分小伙伴和我一样,能回答出“ArrayList 是基于数组实现的,LinkedList 是基于双向链表实现的。”
沉默王二
2020/09/28
6640
面试官:兄弟,说说 ArrayList 和 LinkedList 有什么区别
jdk源码分析之ArrayList
* The array buffer into which the elements of the ArrayList are stored.
全栈程序员站长
2022/07/15
1760
吐血整理!2万字Java基础面试题(带答案)请收好!
HashMap容器O(1)的查找时间复杂度只是其理想的状态,而这种理想状态需要由java设计者去保证。
码老思
2023/10/19
4290
吐血整理!2万字Java基础面试题(带答案)请收好!
引起Java中内存泄露8种场景归纳,一定要避开这些!
OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError。看下关于的官方说明:Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. 意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。
肉眼品世界
2021/03/09
1.7K0
引起Java中内存泄露8种场景归纳,一定要避开这些!
基于JDK1.8,Java容器源码分析
在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码。
李红
2019/06/01
5360
基于JDK1.8,Java容器源码分析
Java集合--List源码
源码 上一篇,我们介绍ArrayList和LinkedList的内容,对于这两个类的源码只列举其中的一部分,本篇就来完整的阐述下!!! 希望能让你对这两个类,有一个更完整的理解! ArrayList 完整源码: public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { //实现Serializable接口,
贾博岩
2018/05/11
1.4K0
【拒绝拖延】常见的JavaScript内存泄露原因及解决方案
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。这里就讲一些常见会带来内存泄露的原因。
拾贰
2019/08/28
1K0
JS内存泄露/垃圾回收/闭包
前端垃圾回收(Garbage Collection, GC)主要由 JavaScript 引擎自动完成,用于释放不再被引用的内存。虽然前端开发者通常不需要手动释放内存,但了解一些常见导致内存泄漏和垃圾回收场景,对性能优化和避免 Bug 很有帮助。
biaoblog.cn 个人博客
2025/05/22
1190
详细解读ThreadLocal的内存泄露
说到内存溢出,我相信各位都知道是什么,但是说到内存泄露,而且还是 ThreadLocal ,阿粉就得来说一下这个了,毕竟如果面试的时候被问到 ThreadLocal 的内存泄露,是不是有可能不太了解了呢,今天阿粉来说一下这个 ThreadLocal 的内存泄露的原因,以及如何从开发中去避免这个问题。
Java极客技术
2022/12/04
1.5K0
详细解读ThreadLocal的内存泄露
内存泄露的原因找到了,罪魁祸首居然是Java ThreadLocal
ThreadLocal使用不规范,师傅两行泪 组内来了一个实习生,看这小伙子春光满面、精神抖擞、头发微少,我心头一喜:绝对是个潜力股。于是我找经理申请亲自来带他,为了帮助小伙子快速成长,我给他分了一个
main方法
2020/12/07
1K0
内存泄露的原因找到了,罪魁祸首居然是Java ThreadLocal
《面试补习》- Java集合知识梳理
从依赖关系可以看出,ArrayList 首先是一个列表,其次,他具有列表的相关功能,支持快速(固定时间)定位资源位置。可以进行拷贝操作,同时支持序列化。这里我们需要重点关注的是 AbstractLit 以及 RandomAccess 。这个类,一个是定义了列表的基本属性,以及确定我们列表中的常规动作。而RandomAccess 主要是提供了快速定位资源位置的功能。
九灵
2021/06/30
5260
《面试补习》- Java集合知识梳理
Java 基础概念·Java List
List 是单列集合 Collection 下的一个实现类,List 的实现接口又有几个,一个是 ArrayList,还有一个是 LinkedList,还有 Vector,这次研究下这三个类的源码。
数媒派
2022/12/01
7060
Android 关于内存泄露,你必须了解的东西
内存管理的目的就是让我们在开发过程中有效避免我们的应用程序出现内存泄露的问题。内存泄露相信大家都不陌生,我们可以这样理解:「没有用的对象无法回收的现象就是内存泄露」。
developerHaoz
2018/08/20
1.2K0
java造成内存泄露原因
一、Java内存回收机制  不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。在Java语言
xiangzhihong
2018/01/30
2.3K0
推荐阅读
相关推荐
猿思考系列9——一文获取隐藏逻辑挖掘办法
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验