An important feature of Java is to automatically manage memory recycling through a garbage collector (GC), without requiring programmers to free up memory by themselves. Theoretically, all memory occupied by objects that will no longer be used in Java can be recycled by GC, but Java also has memory leaks, but its performance is different from C++.
Memory management in JAVA
To understand memory leaks in Java, you must first know how memory in Java is managed.
In Java programs, we usually use new to allocate memory to objects, and these memory spaces are on the heap (Heap).
Here is an example:
public class Simple { public static void main(String args[]){ Object object1 = new Object();//obj1 Object object2 = new Object();//obj2 object2 = object1; //...At this time, obj2 can be cleaned }}Java uses directed graphs for memory management:
In the directed graph, we call obj1 to be accessible, obj2 to be unreachable, and obviously unreachable can be cleaned.
The release of memory, that is, cleaning up unreachable objects, is determined and executed by GC, so GC will monitor the status of each object, including application, citation, citation and assignment. The fundamental principle of releasing an object is that the object will no longer be used:
The object is given a null value, and it has never been called again.
The other is to assign a new value to the object, which reallocates memory space.
Usually, it is believed that the cost of allocating objects on the heap is relatively high, but GC optimizes this operation: in C++, allocating a piece of memory on the heap will look for a piece of appropriate memory to allocate. If the object is destroyed, this piece of memory can be reused; in Java, you want a long strip. Every time a new object is allocated, Java's "heap pointer" moves backwards to an area that has not been allocated. Therefore, the efficiency of Java allocating memory is comparable to that of C++.
But there is a problem with this way of working: if memory is frequently applied, the resources will be exhausted. At this time, GC intervenes in, it reclaims space and makes the objects in the heap more compact. In this way, there will always be enough memory space to allocate.
Reference counting method when gc cleansing: when a reference is connected to a new object, the reference count is +1; when a reference leaves the scope or is set to null, the reference count is -1. When GC finds that this count is 0, it recycles the memory it consumes. This overhead occurs throughout the life of the reference program and cannot handle circular references. So this method is just used to illustrate how GC works, and will not be applied by any Java virtual machine.
Most GCs adopt an adaptive cleaning method (plus other additional techniques for speed-enhancing), mainly based on finding any "live" objects and then using an "adaptive, generational, stop-copy, mark-clean" garbage collector. I won't introduce too much, this is not the focus of this article.
Memory leak in JAVA
Memory leakage in Java, broadly and in layman's terms, is that the memory of objects that will no longer be used cannot be recycled, which is a memory leakage.
Memory leaks in Java are different from those in C++.
In C++, all objects that have been allocated memory must be manually released by the programmer after they are no longer used. Therefore, each class will contain a destructor, which is to complete the cleaning work. If we forget to release certain objects, it will cause memory leakage.
But in Java, we don’t need to (and can’t) free up memory by ourselves, and useless objects are automatically cleaned up by GC, which greatly simplifies our programming work. However, sometimes some objects that will no longer be used cannot be released in the GC, which will cause memory leakage.
We know that objects have life cycles, some are long and some are short. If long life cycle objects hold references with short life cycles, memory leakage is likely. Let's give a simple example:
public class Simple { Object object; public void method1(){ object = new Object(); //...Other code}}In fact, we expect that it will only be used in the method1() method, and it will not be used elsewhere. However, when the method1() method is executed, the memory allocated by the object object will not be immediately considered to be an object that can be released, and will only be released after the object created by the Simple class is released. Strictly speaking, this is a memory leak. The solution is to use object as a local variable in the method1() method. Of course, if you have to write this, you can change it to this:
public class Simple { Object object; public void method1(){ object = new Object(); //...Other code object = null; }}In this way, the memory allocated by "newObject()" can be recycled by GC.
At this point, Java memory leaks should be clearer. Let's explain further below:
When the allocated memory in the heap is not released, all the ways to access this memory are deleted (such as pointer reassignment). This is for languages such as C++. The GC in Java will help us handle this situation, so we don’t need to care.
When the memory object is obviously not needed, it still retains this memory and its access method (reference) which is a memory leak that may occur in all languages. If you are not careful when programming, this is easy to happen, and if it is not too serious, it may be just a brief memory leak.
Some examples and solutions that are prone to memory leakage
The situation like the above example is easy to happen, and it is also the most likely situation we ignore and cause memory leaks. The solution is to minimize the scope of the object (for example, in Androidstudio, the above code will issue a warning, and the suggestions given are to rewrite the member variables of the class into local variables in the method) and manually set the null value.
As for scope, we need to pay more attention when writing code; manually setting the null value, we can take a look at the internal method of deleting the specified node in the Java container LinkedList source code (refer to: Java's LinkedList source code interpretation (JDK1.8) ):
//Delete the specified node and return the deleted element value E unlink(Node<E> x) { //Get the current value and the front and back nodes final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; //If the previous node is empty (such as the current node is the first node), the next node becomes the new first node} else { prev.next = next;//If the previous node is not empty, then it points to the current next node x.prev = null; } if (next == null) { last = prev; //If the next node is empty (such as the current node is a tail node), the previous one of the current node becomes the new tail node} else { next.prev = prev;//If the next node is not empty, the next node points forward to the current previous node x.next = null; } x.item = null; size--; modCount++; return element; }In addition to modifying the relationship between nodes, what we also need to do is to assign a value to null. No matter when GC will start cleaning, we should mark useless objects as cleanable objects in time.
We know that the Java container ArrayList is implemented in an array (refer to: Java's ArrayList source code interpretation (JDK1.8) ). If we want to write a pop() (pop) method for it, it might look like this:
public E pop(){ if(size == 0) return null; else return (E) elementData[--size]; }The writing method is very concise, but it will cause memory overflow here: elementData[size-1] still holds a reference to an E-type object and cannot be recycled by GC for the time being. We can modify it as follows:
public E pop(){ if(size == 0) return null; else{ E e = (E) elementData[--size]; elementData[size] = null; return e; } }When writing code, we cannot blindly pursue simplicity. The first thing is to ensure its accuracy.
Memory leaks during container use
In many articles, you may see an example of memory leaks as follows:
Vector v = new Vector(); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }Many people may not understand it at the beginning, so we can understand the above code as follows:
void method(){ Vector vector = new Vector(); for (int i = 1; i<100; i++) { Object object = new Object(); vector.add(object); object = null; } //...Operations on vector//...Other operations that are not related to vector}Here, memory leaks refer to the fact that after the vector operation is completed, if a GC operation occurs, this series of objects cannot be recycled. The memory leak here may be short-lived, because after the entire method() method is executed, those objects can still be recycled. It is very simple to solve here, just manually assign the value to null:
void method(){ Vector vector = new Vector(); for (int i = 1; i<100; i++) { Object object = new Object(); vector.add(object); object = null; } //...Operations on v vector = null; //...Other operations that are not related to v}The above Vector is outdated, but it is just an introduction to memory leaks using old examples. When we use containers, it is easy to cause memory leakage, just like the example above. However, in the above example, the memory leakage effect caused by local variables in the method during container time may not be very large (but we should also avoid it). However, if this container is a member variable of a class, or even a static member variable, you should pay more attention to memory leakage.
The following is an error that may occur when using containers:
public class CollectionMemory { public static void main(String s[]){ Set<MyObject> objects = new LinkedHashSet<MyObject>(); objects.add(new MyObject()); objects.add(new MyObject()); objects.add(new MyObject()); objects.add(new MyObject()); System.out.println(objects.size()); while(true){ objects.add(new MyObject()); } }}class MyObject{ //Set the default array length to 99999 and happens faster OutOfMemoryError List<String> list = new ArrayList<>(99999);}Running the above code will report an error very quickly:
3Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.ArrayList.<init>(ArrayList.java:152) at com.anxpp.memory.MyObject.<init>(CollectionMemory.java:21) at com.anxpp.memory.CollectionMemory.main(CollectionMemory.java:16)
If you know enough about Java containers, the above error will not happen. Here is also a post that I introduce Java containers:...
The container set only stores unique elements and is compared through the object's equals() method. However, all classes in Java are directly or indirectly inherited to the Object class. The equals() method of the Object class compares the object's address. In the above example, elements will be added until memory overflow.
Therefore, the above example is strictly speaking, memory overflow caused by the wrong use of the container.
As far as Set is concerned, the remove() method also uses the equals() method to delete matching elements. If an object does provide the correct equals() method, remember not to use remove(Objecto) after modifying this object, this may also cause memory leakage.
Various objects that provide close() method
For example, when using database connections (dataSourse.getConnection()), network connections (socket) and io connections, and when using other frameworks, they will not be automatically recycled by GC unless they explicitly call their close() method (or similar method) to close their connection. In fact, the reason is that long-life cycle objects hold references to short-life cycle objects.
Many people may have used Hibernate. When we operate the database, we obtain a session through the SessionFactory:
Session session=sessionFactory.openSession();
After completion, we must call the close() method to close:
session.close();
SessionFactory is a long-life object, and session is relatively short-life object, but the framework is designed in this way: it does not know how long we will use session, so it can only provide a way for us to decide when we will no longer use it.
Because an exception may be thrown before the close() method is called, so the method cannot be called. We usually use the try language, and then finally execute close() and other cleaning work in the statement:
try{ session=sessionFactory.openSession(); //...Other operations} finally{ session.close(); }Memory leaks caused by singleton mode
In many cases, we can regard its life cycle as similar to the life cycle of the entire program, so it is an object with a long life cycle. If this object holds references to other objects, memory leaks are also prone to occur.
References to internal classes and external modules
In fact, the principle is still the same, but the way it appears is different.
Methods related to cleaning
This section mainly talks about gc() and finalize() methods.
gc()
For programmers, GC is basically transparent and invisible. The function that runs GC is System.gc(), and after calling it, it starts the garbage collector and starts cleaning.
However, according to the Java language specification definition, this function does not guarantee that the JVM's garbage collector will execute. Because, different JVM implementers may use different algorithms to manage GC. Generally, GC's threads have lower priority.
There are many strategies for JVM to call GC. Some of them only start working when the memory usage reaches a certain level. Some execute them regularly. Some execute GC smoothly, and some execute GC in interrupt manner. But generally speaking, we don't need to care about this. Unless in some specific situations, the execution of GC affects the performance of the application. For example, for real-time web-based systems such as online games, users do not want GC to suddenly interrupt application execution and perform garbage collection, then we need to adjust the parameters of GC so that GC can free memory in a smooth manner, such as decomposing garbage collection into a series of small steps to execute. The HotSpotJVM provided by Sun supports this feature.
finalize()
finalize() is a method in the Object class.
Those who know C++ know that there is a destructor, but note that finalize() is by no means equal to the destructor in C++.
This is explained in Java programming idea: Once GC is ready to free up the storage space occupied by the object, its finalize() method will be called first, and the memory occupied by the object will be recycled only when the next GC recycling action occurs, so we can put some cleaning work in finalize().
An important purpose of this method is: when calling non-java code (such as c and c++) in java, corresponding memory-applied operations (such as c's malloc() function) may be used in these non-java codes, and in these non-java codes, these memory does not release these memory effectively, you can use the finalize() method and call local method free() and other functions.
Therefore finalize() is not suitable for ordinary cleaning.
However, sometimes, this method has some uses:
If there is a series of objects, one of the objects has a state of false. If we have processed this object, the state will become true. In order to avoid missing objects without processing, you can use the finalize() method:
class MyObject{ boolean state = false; public void deal(){ //...some processing operations state = true; } @Override protected void finalize(){ if(!state){ System.out.println("ERROR:" + "Object not processed!"); } } //...}But from many aspects, this method is recommended not to be used and considered redundant.
In general, memory leaks are caused by poor encoding. We cannot blame the JVM for not cleaning up more reasonably.
Summarize
The above is all the detailed explanation of the memory leaked code in the Java language. I hope it will be helpful to everyone. Interested friends can continue to refer to other related topics on this site. If there are any shortcomings, please leave a message to point it out. Thank you friends for your support for this site!