解决内存泄漏问题

内存泄露问题

内存泄漏是指不使用的对象持续占有内存使得内存得不到释放,从而造成内存空间的浪费。

内存泄露导致问题:

  • 最明显问题频繁GC,从而STW次数增加,导致用户体验变差;
  • 如果内存泄露问题严重,会导致OOM,直接导致程序不能正常运行;

内存泄露是很严重的问题,在出现内存泄露的情况下,想要解决是肯定要修改代码的,所以在编写代码的时候要避免出现内存泄露。

内存泄露原因

大多数内存泄露的原因是,长生命周期的对象引用了短生命周期的对象。例如,A对象引用B对象,A对象的生命周期(t1-t4)比B对象的生命周期(t2-t3)长的多。当B对象没有被应用程序使用之后,A对象仍然在引用着B对象。这样,垃圾回收器就没办法将B对象从内存中移除,从而导致内存泄露问题。

变量作用域不合理导致内存泄露

一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏

public class UsingRandom {
    private String msg;
    public void receiveMsg(){
        readFromNet();//从网络中接受数据保存到msg中
        saveDB();//把msg保存到数据库中
    }
}

解决办法,将msg定义在receiveMsg方法中或将msg重置为null。

public class UsingRandom {
    private String msg;
    public void receiveMsg(){
        readFromNet();//从网络中接受数据保存到msg中
        saveDB();//把msg保存到数据库中
        msg = null;
    }
}
向静态集合添加数据导致内存泄露

HashMap、LinkedList 等集合类,如果这些集合是静态的并且向集合中添加了对象,这些对象就算不再使用,也不会被GC主动回收的,它们的生命周期与JVM程序一致,容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。

public class MyTest {
    static Map<String, User> map = new HashMap<>();
    public static void main(String[] args) throws InterruptedException {
        User user = new User();
        map.put("01",user);
    }
}

解决办法是将使用完后的集合和对象重置为null,或将集合替换成弱引用集合(只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存)。

    static Map<String, User> map = new HashMap<>();
    public static void main(String[] args) throws InterruptedException {

        User user = new User();
        map.put("01",user);

        user = null;
        map = null;

        System.gc();
        Thread.sleep(1000);
    }
    static Map<String, User> map = new WeakHashMap<>();
    public static void main(String[] args) throws InterruptedException {
        User user = new User();
        map.put("01",user);
    }
内部类持有外部类引用导致内存泄露

非静态内部类,自动生成的构造方法,默认的参数是外部类的类型,因此使用非内部内部类的时候会保留一个外部类的引用。如果换成静态内部类则不会生成默认的构造方法。

public class MyClass {


    public static void main(String[] args) throws Throwable {

    }

    public class A{
        public void methed1(){

        }
    }

    public static  class B{
        public void methed1(){

        }
    }
}

如果一个外部类的实例对象的方法返回了一个内部类的实例对象。这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。

数据结构中移除元素导致内存泄露
  • 链表中移除元素,没有将移除的元素置为null导致的内存泄露问题

Java容器LinkedList移除元素方法核心源码:


//删除指定节点并返回被删除的元素值
E unlink(Node<E> x) {
    //获取当前值和前后节点
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    if (prev == null) {
        first = next; //如果前一个节点为空(如当前节点为首节点),后一个节点成为新的首节点
    } else {
        prev.next = next;//如果前一个节点不为空,那么他先后指向当前的下一个节点
        x.prev = null;
    }
    if (next == null) {
        last = prev; //如果后一个节点为空(如当前节点为尾节点),当前节点前一个成为新的尾节点
    } else {
        next.prev = prev;//如果后一个节点不为空,后一个节点向前指向当前的前一个节点
        x.next = null;
    }
    x.item = null; // help gc
    size--;
    modCount++;
    return element;
}

除了修改节点间的关联关系,我们还要做的就是赋值为null的操作,不管GC何时会开始清理,我们都应及时的将无用的对象标记为可被清理的对象。


  • 在栈中将该元素出栈,没有将出栈的元素置为null导致的内存泄露问题
public E pop(){
    if(size == 0)
        return null;
    else{
        E e = (E) elementData[--size];
        elementData[size] = null; // help gc
        return e;
    }
}

  • 改变hash表中对象的属性值,再将该元素移除导致的内存泄露问题

因为改变了对象属性的值相当于改变了改对象的hash值,删除的时候是根据对象的hash值来删除的,删除对象的时候找不到对应的hash值,所以不能删除,最终导致内存泄露。

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"; // 导致了内存的泄漏
        set.remove(p1);  // 对象哈希值发生变化,检索不到,导致删除失败
 
        System.out.println(set);
 
        set.add(new Person(1001, "CC"));
        System.out.println(set);
 
        set.add(new Person(1001, "AA"));
        System.out.println(set);
    }
}
 
class Person {
    int id;
    String name;
 
    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
 
        Person person = (Person) o;
 
        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 + '\'' +
                '}';
    }
}
连接未释放

如数据库连接、网络连接和IO连接等。当不再使用时,需要调用close方法来释放连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在连接过程中,对一些对象不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。

public static void main(String[] args) {
    try{
        Connection conn =null;
        Class.forName("com.mysql.jdbc.Driver");
        conn =DriverManager.getConnection("url","","");
        Statement stmt =conn.createStatement();
        ResultSet rs =stmt.executeQuery("....");
    } catch(Exception e){
    } finally {
        // 1.关闭结果集 Statement
        // 2.关闭声明的对象 ResultSet
        // 3.关闭连接 Connection
    }
}
  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值