One article to get through memory leaks in java

Table of contents

Pre-knowledge

 memory leak

Memory overflow (out of memory)

8 cases of memory leaks in Java

static collection class

 singleton pattern

Inner class holds outer class

Various connections, such as database connections, network connections and IO connections, etc.

Unreasonable scoping of variables

change the hash

cache leak

Listeners and other callbacks


Pre-knowledge

 memory leak

The reachability analysis algorithm to determine whether an object is no longer used is essentially to determine whether an object is still referenced. So in this case, due to the different implementation of the code, there will be many kinds of memory leak problems (making the JVM mistakenly think that the object is still in reference and cannot be recycled, resulting in memory leaks).

> Is it still used? yes

> Is it still needed? no

 Strictly speaking, only when the objects are no longer used by the program, but the GC cannot reclaim them, is it called a memory leak. But in the actual situation, some bad practices (or negligence) will cause the life cycle of the object to become very long or even lead to 00M, which can also be called "memory leak" in a broad sense.

As shown in the figure below, when the life cycle of Y ends, X still refers to Y. At this time, the garbage collection period will not recycle object Y; if object X still refers to A, B, and C with shorter life cycles, object A It also refers to objects a, b, and c, which may cause a large number of useless objects that cannot be recycled, and then occupy memory resources, causing memory leaks until memory overflows.

The application is not to release the memory when it is used up. For example, there is a total of 1024M of memory, and the allocated memory of 512M has not been reclaimed, so the usable memory is only 512M, as if part of it has been leaked.

Memory overflow (out of memory)

When applying for memory, there is not enough memory available; generally speaking, there are three pits in one toilet, two of which are standing in the toilet (memory leak), and the last pit is left. The toilet indicates that the reception pressure is very high. At this time, two people came at once, and the pit (memory) was not enough, and the memory leak became a memory overflow. It can be seen that the relationship between memory leaks and memory overflow: the increase of memory leaks will eventually lead to memory overflow.

Classification of leaks

  1. Occurs often: the code with memory leak will be executed multiple times, and each time it is executed, a piece of memory will be leaked;
  2. Occurs by chance: Occurs only under certain circumstances
  3. One-time: the method with a memory leak will only be executed once;
  4. Implicit leak: It keeps occupying memory and does not release it until the end of execution; strictly speaking, this is not a memory leak, because it is finally released, but if the execution time is particularly long, it may also cause memory exhaustion.

8 cases of memory leaks in Java

static collection class

Static collection classes, such as HashMap, LinkedList, etc. If these containers are static, then their life cycle is consistent with the JVM program, and the objects in the container will not be released before the program ends, resulting in memory leaks. Simply put, long-lived objects hold references to short-lived objects. Although short-lived objects are no longer used, they cannot be recycled because long-lived objects hold references to them. 

public class MemoryLeak {
    static List list = new ArrayList();
    public void oomTests(){
        Object obj=new Object();//局部变量
        list.add(obj);
    }
}

 singleton pattern

The singleton mode is similar to the cause of memory leaks caused by static collections . Because of the static nature of the singleton, its life cycle is as long as the life cycle of the JVM, so if the singleton object holds a reference to an external object, then the external object Will not be recycled, then it will cause a memory leak.

Inner class holds outer class

The inner class holds the outer class, if a method of an instance object of the outer class returns an instance object of the inner class. This inner class object has been referenced for a long time, even if the outer class instance object is no longer used, but because the inner class holds the outer class instance object, the outer class object will not be garbage collected , which will also cause a memory leak.

Various connections, such as database connections, network connections and IO connections, etc.

In the process of operating the database, it is first necessary to establish a connection with the database, and when it is no longer in use, it is necessary to call the close method to release the connection with the database. Only after the connection is closed, the garbage collector will reclaim the corresponding object . Otherwise, if the Connection, Statement or ResultSet is not explicitly closed during the process of accessing the database, a large number of objects will not be reclaimed, resulting in memory leaks.

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
    }
}

Unreasonable scoping of variables

Unreasonable scoping of variables. Generally speaking, the scope of a variable's definition is greater than its scope of use, which is likely to cause memory leaks. On the other hand, if the object is not set to null in time, it is likely to cause a memory leak.

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

 As in the pseudo code above, the received message is saved in the variable msg through the readFromNet method, and then the saveDB method is called to save the content of msg to the database. At this time, msg is useless, because the life cycle of msg and the life of the object The cycle is the same, and msg cannot be recycled at this time, thus causing a memory leak. In fact, this msg variable can be placed inside the receiveMsg method. When the method is used up, the life cycle of msg will end, and it can be recycled at this time . There is another way to set msg to null after using msg, so that the garbage collector will also reclaim the memory space of msg.

change the hash

Change the hash value. When an object is stored in the HashSet collection, the fields in the object that participate in the calculation of the hash value cannot be modified.

Otherwise, the modified hash value of the object is different from the hash value when it was originally stored in the HashSet collection. In this case, even if the contains method uses the current reference of the object as a parameter to retrieve the object in the HashSet collection , will also return the result that the object cannot be found, which will also result in the inability to delete the current object from the HashSet collection alone, resulting in a memory leak.

This is why String is set as an immutable type. We can safely store String in HashSet, or use String as the key value of HashMap; when we want to save the class we define to the hash table, we need to ensure the hashCode is immutable.

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 + '\'' +
                '}';
    }
}
/**
 * 例2
 */
public class ChangeHashCode1 {
    public static void main(String[] args) {
        HashSet<Point> hs = new HashSet<Point>();
        Point cc = new Point();
        cc.setX(10);//hashCode = 41
        hs.add(cc);

        cc.setX(20);//hashCode = 51  此行为导致了内存的泄漏

        System.out.println("hs.remove = " + hs.remove(cc));//false
        hs.add(cc);
        System.out.println("hs.size = " + hs.size());//size = 2

        System.out.println(hs);
    }

}

class Point {
    int x;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        Point other = (Point) obj;
        if (x != other.x) return false;
        return true;
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                '}';
    }
}

cache leak

Another common source of memory leaks is caching. Once you put object references in the cache, they are easy to forget. For example: when the previous project was launched for the first time, the application started very slowly until it crashed to death, because the code would load the data in a table into the cache (memory), the test environment only had a few hundred pieces of data, but the production environment had hundreds million data.

For this problem, WeakHashMap can be used to represent the cache. The characteristic of this kind of Map is that when the key has no other references except its own reference to the key, the map will automatically discard this value.

public class MapTest {
    static Map wMap = new WeakHashMap();
    static Map map = new HashMap();

    public static void main(String[] args) {
        init();
        testWeakHashMap();
        testHashMap();
    }

    public static void init() {
        String ref1 = new String("obejct1");
        String ref2 = new String("obejct2");
        String ref3 = new String("obejct3");
        String ref4 = new String("obejct4");
        wMap.put(ref1, "cacheObject1");
        wMap.put(ref2, "cacheObject2");
        map.put(ref3, "cacheObject3");
        map.put(ref4, "cacheObject4");
        System.out.println("String引用ref1,ref2,ref3,ref4 消失");

    }

    public static void testWeakHashMap() {
        System.out.println("WeakHashMap GC之前");
        for (Object o : wMap.entrySet()) {
            System.out.println(o);
        }
        try {
            System.gc();
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("WeakHashMap GC之后");
        for (Object o : wMap.entrySet()) {
            System.out.println(o);
        }
    }

    public static void testHashMap() {
        System.out.println("HashMap GC之前");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
        try {
            System.gc();
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("HashMap GC之后");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
    }

}

The above code and illustration show how WeakHashMap automatically releases the cache object. After the init function is executed, the local variable string references weakd1, weakd2, d1, and d2 will disappear. At this time, only the reference to the string object stored in the static map , it can be seen that after calling gc, the HashMap is not recycled, but the cache in WeakHashMap is recycled.

Listeners and other callbacks

A third common source of memory leaks is listeners and other callbacks, which can build up if clients register callbacks in the API you implement without explicit cancellation. The best way to ensure that the callback is immediately garbage collected is to save only weak references to it , for example by saving them as keys in a WeakHashMap.

Guess you like

Origin blog.csdn.net/m0_62436868/article/details/130318943