内存泄漏(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工具查看堆内存的信息如下

JVM内存泄漏未关闭的资源例子
跟上面一样,内存的占用也是处于上升的趋势。

所以对于io操作,我们在打开连接后需要关闭连接,否则会引发内存泄漏。

equals()和hashCode()实现不正确

这里耐心看完,在HashMap的put源码中对key的值有一个key.hashCode()方法,如下

HashMap 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

那么,如果重复的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工具查看堆内存的信息如下

JVM内存泄漏equals()和hashCode()实现不正确