内存泄漏(Leak)的意思是GC无法回收内存中的对象,导致内存溢出。
就是说程序在heap堆中new了一些对象,GC认为这些对象是可用的,不能回收,久而久之,随着程序的运行会导致内存不够用了,这就是内存泄漏Leak。
下面给出JVM内存泄漏(Leak)的例子。
静态集合类变量内存泄漏的例子
import java.util.ArrayList;
import java.util.List;
public class StaticFieldsMemoryLeakExample extends Thread{
//这个NUMBERS静态变量gc回收不了
public static List<Integer> NUMBERS = new ArrayList<>();
public void addBatch() {
for (int i = 0; i < 100000; i++) {
NUMBERS.add(i);
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000000; i++) {
//调用add方法1000000次
(new StaticFieldsMemoryLeakExample()).addBatch();
//主动回收
System.gc();
Thread.sleep(10000);
}
}
}
通过Visual VM工具查看堆内存的信息如下
在程序主动执行gc回收的情况下,它的内存占用还是以递增的方式呈现的,因为NUMBERS集合是静态的,它并不会释放内存的对象。
所以在使用静态变量时要格外小心,在不需要静态集合中的数据时,需要手动删除掉。
未关闭的资源例子
比如网络请求连接,数据库连接,io处理等没有关闭资源。
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class UncloseResourceLeakExample extends Thread{
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000000; i++) {
URL url = new URL("http://www.baidu.com");
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
//
}
}
}
通过Visual VM工具查看堆内存的信息如下
所以对于io操作,我们在打开连接后需要关闭连接,否则会引发内存泄漏。
equals()和hashCode()实现不正确
这里耐心看完,在HashMap的put源码中对key的值有一个key.hashCode()方法,如下即hashmap中真正的key是key对象的hashCode()方法的值。如果在我们没有重写这个hashCode()方法,它每次返回的值是不一样的,它关联的是对象的内存地址,源码请参考 Java对象hashCode()源码。
//Foo.java
public class Foo {
public int id;
public Foo(int id) {
this.id = id;
}
}
//HelloWorld.java
public class HelloWorld extends Thread{
public static void main(String[] args) throws Exception {
Foo foo = new Foo(1);
Foo foo2 = new Foo(1);
System.out.println(foo.hashCode());
System.out.println(foo2.hashCode());
}
}
运行这个例子,会得到两个不一样的hash值。460141958
1163157884
1163157884
那么,如果重复的new一个对象作为hashmap的key,并不会被覆盖,因为它们的key不同,这种情况便会出现内存泄漏。
//Foo.java
public class Foo {
public int id;
public Foo(int id) {
this.id = id;
}
}
import java.util.HashMap;
import java.util.Map;
//HelloWorld.java
public class HelloWorld extends Thread{
public static void main(String[] args) throws Exception {
Map<Foo, Integer> map = new HashMap<Foo, Integer>();
for(int i = 0; i < 100000; i++) {
Thread.sleep(1);
map.put(new Foo(1), 1);
}
}
}
同样的,通过Visual VM工具查看堆内存的信息如下