0%

内存泄漏

Java有很好的GC垃圾回收器,帮助我们自动地管理内存,定期删除未引用的对象,但并不是万无一失,仍然可能会造成内存泄漏。

什么是内存泄漏

在堆内存中产生了不再使用的对象,但垃圾回收器并没有把它们从内存中释放,因此造成了不必要的内存占用,造成系统性能降低,造成java.lang.OutOfMemoryError错误。

Java中内存泄漏的类型

内存泄漏的原因有很多,下面有几种最常见的。

静态变量的大量使用

静态变量的生命周期通常和整个程序的生命周期相同,以下程序会造成大量数据浪费。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class StaticTest {
public static List<Double> list = new ArrayList<>();

public void populateList() {
for (int i = 0; i < 10000000; i++) {
list.add(Math.random());
}
Log.info("Debug Point 2");
}

public static void main(String[] args) {
Log.info("Debug Point 1");
new StaticTest().populateList();
Log.info("Debug Point 3");
}
}
  • 应尽量减少静态变量的使用
  • 使用单例时,尽量使用懒汉式,不使用饿汉式,即应在使用时再创建实例。

资源使用后未关闭

新连接的建立、stream流的打开等资源使用,都需要占用内存,如果不手动关闭,它们将不会被GC回收。

  • 使用finally代码块关闭资源
  • 关闭资源时不能报错
  • JDK版本7或以上,使用try-with-resources代码块

equals()和hashCode()不正确使用

以下程序的map中,同名的对象出现了100次,造成了内存浪费。

1
2
3
4
5
6
7
public class Person {
public String name;

public Person(String name) {
this.name = name;
}
}

K的Hash值相等,并且K进行equels返回true,才能覆盖V值。

1
2
3
4
5
6
7
@Test
public void givenMap_whenEqualsAndHashCodeNotOverridden_thenMemoryLeak() {
Map<Person, Integer> map = new HashMap<>();
for(int i=0; i<100; i++) {
map.put(new Person("jon"), 1);
}
}

重写类后,map中只存在一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Person {
public String name;

public Person(String name) {
this.name = name;
}

@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Person)) {
return false;
}
Person person = (Person) o;
return person.name.equals(name);
}

@Override
public int hashCode() {
int result = 17;
result = 31 * result + name.hashCode();
return result;
}
}
  • 定义实体类时,尽量重写equals和hashcode方法

引用外部类的内部类

非静态内部类(匿名类)默认有一个对外部类的引用,如果使用内部类的对象,外部类的对象就不会被回收,造成内存泄漏

  • 使用静态内部类

参考资料