JVM之内存泄露
什么是内存泄露?
严格来说,对象不再被程序使用,但是GC又不能回收他们的情况(被GC Root所引用,不能被回收), 就叫内存泄漏。但实际很多时候一些疏忽会导致对象的生命周期变得很长甚至和JVM的生命周期一样,可能会导致内存溢出,也可叫做宽泛意义上的“内存泄漏”。
内存泄露的8种情况
- 静态集合类
public class MemoryLeak {
static List list = new ArrayList<>();
public void oomTest() {
Object obj = new Object(); //当前方法结束后,obj因为被list引用,仍无法回收,导致内存泄露。
list.add(obj);
}
}
-
单例模式
静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和 JVM 的生命周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。
-
内部类持有外部类
内部类的对象被其他生命周期长的对象长期持有,那么内部类对象所属的外部类对象也不会被回收。
-
各种连接,如数据库连接
public static void main(String[] args) { try { Connection conn = null; Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("url","",""); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("......"): } catch(Exception e) { } finally { //1.关闭结果集对象rs //2.关闭数据库对象 //3.关闭连接 } }
如果不将关闭资源的语句放在finally语句块中,如果在执行的过程中发生了异常,就无法正常关闭资源,就会导致内存泄漏。
-
变量不合理的作用域
public class UsingRandom { private String msg; public void receiveMsg() { readFromNet(); //从网络中接收数据保存到msg中 saveDB(); //把msg保存到数据库中 } }
通过readFromNet方法把接受的消息保存在变量msg中,然后调用saveDB方法把msg的内存保存到数据库中,此时msg就已经没用了,由于msg的生命周期和当前对象的生命周期相同,此时msg还不能回收,因此造成了内存泄露。所以msg变量应该放在receiveMsg方法内,当使用完就可以被回收。
-
改变哈希值
当一个对象被存储进Hashset集合中,就不能修改这个对象中的那些参与计算哈希值的字段。如果修改了,就不能通过对象的当前引用作为参数去检索对象,造成内存泄漏。
这也是 String 为什么被设置成了不可变类型,我们可以放心地把 String 存入 HashSet,或者把 String 当做 HashMap 的 key 值;
public class ChangeHashCode { public static void main(String[] args) { HashSet set = new HashSet(); Person p1 = new Person(1001, "AA"); Person p2 = new Person(1002, "BB"); set.add(p1); set.add(p2); p1.name = "CC"; //修改了参与计算哈希值的字段,导致不能通过p1找到Hashset中的对象,导致了内存泄露 set.remove(p1); //删除失败 System.out.println(set); } } class Person { public int id; public String name; public Person(int id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object obj) { if(this == obj) return true; if(!(obj instanceof Person)) return false; Person person = (Person) obj; if(id != person.id) return false; return name != null ? name.equals(person.name) : person.name == null; } @Override public int hashCode() { int result = id; result = 31 * result + (name != null ? name.hashCode() : 0); return result; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
-
缓存泄露
一旦数据被放到缓存中,就很容易被遗忘,导致内存泄漏。所以可以使用弱引用代替缓存,一旦发生GC就会被清除掉。
将以HashMap数据结构作为缓存替换为WeakHashMap.
-
监听器和回调