博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA内存泄露的原因及解决
阅读量:5874 次
发布时间:2019-06-19

本文共 4486 字,大约阅读时间需要 14 分钟。

1. 概述

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

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

2. 什么是内存泄露

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

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

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

3.1. 问题描述

public class Simple {    private Object object;    public void method() {        object = new Object();        // ...    }}复制代码

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

3.2. 改进

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

public class Simple {    private Object object;    public void method() {        object = new Object();        // 使用到 object 的业务代码        object = null;    }}复制代码

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

下面是一段 jdk8 LinkedList 的源码。

//删除指定节点并返回被删除的元素值E unlink(Node
x) { //获取当前值和前后节点 final E element = x.item; final Node
next = x.next; final Node
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 方法。

public E pop(){    if(size == 0)        return null;    else        return (E) elementData[--size];}复制代码

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

4.2. 改进

public E pop(){    if(size == 0)        return null;    else{        E e = (E) elementData[--size];        elementData[size] = null;        return e;    }}复制代码

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

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

5.1. 问题描述

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. 改进

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. 问题描述

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
method(List
testList) throws CloneNotSupportedException { Set
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 方法的实现即可。

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
method(List
testList) throws CloneNotSupportedException { Set
result = new HashSet<>(); for (int a = 0; a < 100000) { for (TestClass test : testList) { result.add(test.clone()); } } }}复制代码

note:预防为主,治疗为辅

转载于:https://juejin.im/post/5c9ccaff6fb9a070fe0ded51

你可能感兴趣的文章
Auto 和 Decltye 的区别
查看>>
常用sql语句
查看>>
submit与button区别提交区别
查看>>
远程推送,集成极光的SDK,证书制造
查看>>
LeetCode-114. Flatten Binary Tree to Linked List
查看>>
Zedboard安装桌面系统ubuntu及opencv(2)
查看>>
函数声明优先级高于变量赋值
查看>>
20151217jqueryUI--自动补全工具
查看>>
链接脚本与重定位
查看>>
Hibernate 框架基本知识
查看>>
keystone nova v2 python
查看>>
VMware虚拟机Bridged(桥接模式)
查看>>
hdu4747 线段树区间修改值,区间查询和及最大值即最大值位置
查看>>
Python 字符串、列表、字典 操作方法大全 & 正则re
查看>>
Vue.js 介绍及其脚手架工具搭建
查看>>
Register code
查看>>
oracle基础入门(二)
查看>>
java 基础知识-数组的7种算法(排序、求和、最值、遍历...)
查看>>
倒要看看你有啥本事
查看>>
bzu-java(三)
查看>>