JVM之内存泄露

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.

  • 监听器和回调

内存泄露和内存溢出的关系是内存泄露的堆积最终会导致内存溢出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值