(JVM) shallow heap deep heap and memory leaks

​Shallow heap, deep heap and memory leaks

1. Shallow Heap

Shallow heap refers to the memory consumed by an object. In a 32-bit system, an object reference will occupy 4 bytes, an int type will occupy 4 bytes, a long type variable will occupy 8 bytes, and each object header will occupy 8 bytes. Depending on the heap snapshot format, object sizes may be aligned to 8 bytes.

Take String as an example: 2 int values ​​occupy a total of 8 bytes, object references occupy 4 bytes, and object headers occupy 8 bytes, totaling 20 bytes, aligned to 8 bytes, so occupying 24 bytes. (in jdk7)

int hash32 0
int hash 0
ref value C:\Users\Administrat

These 24 bytes are the shallow heap size for String objects. It has nothing to do with the actual value of String, regardless of the length of the string, the shallow heap size is always 24 bytes.

2. Retained Set

The retained set of object A refers to all object collections (including object A itself) that can be released when object A is garbage collected, that is, the retained set of object A can be considered as only directly or indirectly accessible through object A The collection of all objects in . In layman's terms, it refers to the collection of objects held only by object A.

3. Retained Heap

The deep heap is the sum of the shallow heap sizes of all objects in the object's retain set.

Note: The shallow heap refers to the memory occupied by the object itself, excluding the size of its internal reference objects. The deep heap of an object refers to the sum of the shallow heaps of all objects that can only be accessed (directly or indirectly) through the object, that is, the real space that can be released after the object is recycled.

4. The actual size of the object

Here, the actual size of an object is defined as the sum of the shallow heap sizes of all objects that an object can touch, which is what we usually call the object size. Compared with deep heap, it seems that this is more intuitive and accepted in daily development, but in fact, this concept has nothing to do with garbage collection.

The figure below shows a simple object reference graph, object A references C and D, and object B references C and E. Then the shallow heap size of object A is only A itself, excluding C and D, and the actual size of A is the sum of A, C, and D. However, the deep heap size of A is the sum of A and D. Since object C can also be accessed through object B, it is not within the deep heap range of object A.

5. Dominator Tree

The concept of a dominator tree comes from graph theory. MAT provides an object graph called Dominator Tree. The dominance tree reflects the dominance relationship between object instances. Object A is said to dominate object B if all paths to object B pass through object A in the object reference graph. Object A is considered to be the immediate dominator of object B if object A is the closest dominator to object B. The dominator tree is built based on the reference graph between objects, and it has the following basic properties:

  • The subtree of object A (the collection of all objects dominated by object A) represents the retained set of object A (retained set), that is, the deep heap.

  • If object A dominates object B, then the immediate dominator of object A also dominates object B.

  • The edges of the dominator tree do not correspond directly to the edges of the object reference graph.

As shown in the figure below: the left figure represents the object reference graph, and the right figure represents the dominator tree corresponding to the left figure. Objects A and B are directly dominated by the root object. Since the path to object C can pass through A or B, the direct dominator of object C is also the root object. Object F and object D refer to each other, because all paths to object F must pass through object D, therefore, object D is the direct dominator of object F. All paths to object D must pass through object C, even if the reference from object F to object D starts from the root node, it also passes through object C, so the direct dominator of object D is object C. Similarly, object E dominates object G. Object H can be reached through object D or object E, so neither object D nor E can dominate object H, but can reach both D and E through object C, so object C is the direct dominator of object H.

 

6. 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. In this case, there will be many kinds of memory leaks due to different code implementations (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

(Above: [Yes, Yes] is not a memory leak; but [Yes, No] is a problem, it is a memory leak, such as Fogotten Reference->Memory Leak on the right side of the figure below )

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, object X refers to object Y, and the life cycle of X is longer than that of Y. 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 the object X also refers to A, B, and C with relatively short life cycles, and object A 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 Out of memory.

If the memory is used up and not released, for example, there is a total of 1024M of memory, and the allocated memory of 512M has not been reclaimed, then the usable memory is only 512M, as if part of it has been leaked; in layman’s terms, a memory leak is [occupancy] Maokeng does not pull shi]

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

  • 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;

  • Accidental occurrence : It will only happen under certain circumstances (for example, some resources in the program need to be closed at the end, but it may not be executed down to close due to some previous exceptions, resulting in the resource not being closed)

  • One-time : the method with a memory leak will only be executed once;

  • 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. 8 cases of memory leaks in Java

8.1. Static collection classes

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

8.2. 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.

8.3. Inner classes hold outer classes

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.

8.4. 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 used, 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 a memory leak.

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

8.5. 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 method, after using msg, set msg to null, so that the garbage collector will also reclaim the memory space of msg.

The code above should be changed to: 

8.6. Changing the hash value

Change the hash value. After 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 that the hashCode of the object is immutable.

/**
 * 例1
 */
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 +
                '}';
    }
}

8.7. Cache leaks

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 . When 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.

8.8. Listeners and other callbacks

A third common source of memory leaks is listeners and other callbacks, which can accumulate if clients register callbacks in your implemented API 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.

9. Memory Leak Case Analysis

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) { //入栈
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() { //出栈
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

There is no obvious error in the above program, but this program has a memory leak. With the increase of GC activity or the continuous increase of memory usage, the performance of the program will be reduced. In severe cases, it can lead to memory leaks, but this Failures are relatively rare.

The main problem of the code is in the pop function, which is shown by this diagram below. Assuming that the stack has been growing, as shown in the figure below after growth

When a large number of pop operations are performed, because the reference is not emptied, gc will not release it, as shown in the figure below

It can be seen from the above figure that if the stack grows first and then shrinks, the objects popped from the stack will not be garbage collected, even if the program no longer uses these queue objects in the stack, they will not be recycled , because the reference of this object is still saved in the stack, commonly known as expired reference , this memory leak is very hidden.

Change the pop() method in the code to the following method:

public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;
    return result;2
}

Once the references expire, clear those references and make the references empty.

Guess you like

Origin blog.csdn.net/QRLYLETITBE/article/details/128706190