This article introduces the principles of Java memory management and the causes of memory leaks in detail. It also provides some solutions to solve Java memory leaks. I hope it will be helpful to all Java developers.
Java memory management mechanism
In C++, if a piece of memory is needed to dynamically allocate a piece of memory, the programmer needs to be responsible for the entire life cycle of this piece of memory. From application for allocation, to use, to final release. This process is very flexible, but very cumbersome. Programmers are prone to forgetting to free memory due to negligence, resulting in memory leakage. The Java language has made its own optimization for memory management, which is the garbage collection mechanism. Almost all memory objects in Java are allocated on heap memory (except for basic data types), and GC (gage collection) is then responsible for automatically recycling memory that is no longer used.
The above is the basic situation of the Java memory management mechanism. But if we only understand this, we will still encounter memory leaks in actual project development. Some people may doubt that since Java's garbage collection mechanism can automatically recycle memory, why will there still be memory leaks? In this question, we need to know when GC recycles memory objects and what kind of memory objects will be considered "no longer used" by GC.
Access to memory objects in Java uses reference methods. In Java code, we maintain a reference variable of a memory object. Through the value of this reference variable, we can access the memory object space in the corresponding memory address. In Java programs, this reference variable itself can be stored in heap memory and in the memory of the code stack (same as the basic data type). GC threads start tracking from reference variables in the code stack to determine which memory is being used. If the GC thread cannot track a piece of heap memory in this way, then the GC believes that this piece of memory will no longer be used (because the code can no longer access this piece of memory).
Through this directed graph memory management method, when a memory object loses all references, GC can recycle it. Conversely, if the object still has a reference, it will not be recycled by GC, even if the Java virtual machine throws OutOfMemoryError.
Java memory leak
Generally speaking, there are two situations for memory leaks. In one case, in C/C++ language, all the allocated memory in the heap will be deleted (such as pointer reassignment) when it is not released; One situation is that when the memory object is obviously no longer needed, it still retains this memory and its access method (reference). The first case is that it has been well solved in Java due to the introduction of the garbage collection mechanism. Therefore, memory leaks in Java mainly refer to the second case.
Maybe just talking about the concept is too abstract, you can take a look at such an example:
The code copy is as follows:
Vector v = new Vector( 10 );
for ( int i = 1 ;i < 100 ; i ++ ){
Object o = new Object();
v.add(o);
o = null ;
}
In this example, there are references to Vector object v and references to Object object o in the code stack. In the For loop, we continuously generate new objects, then add them to the Vector object, and then empty the o reference. The question is, if GC occurs after the o reference is empty, will the Object object we create be recycled by GC? The answer is no. Because when GC tracks references in the code stack, it will find a v reference, and continues to track down, it will find that there is a reference to the Object object in the memory space pointed to by the v reference. That is to say, although the o reference has been empty, there are still other references in the Object object and can be accessed, so the GC cannot release it. If the Object object has no effect on the program after this loop, then we think that a memory leak occurred in this Java program.
Although Java memory leaks are less destructive to memory leaks in C/C++, the program can still run normally in most cases except for a few cases where the program crashes. However, when mobile devices have strict restrictions on memory and CPU, Java memory overflow will lead to inefficiency of programs and occupancy of large amounts of unwanted memory. This will cause the performance of the entire machine to deteriorate, and in severe cases it will also cause the OutOfMemoryError to be thrown, causing the program to crash.
Avoid memory leaks in general
In general, without involving complex data structures, Java memory leaks manifest as the life cycle of a memory object exceeds the length of time the program needs it. We sometimes call it "object free".
For example:
The code copy is as follows:
public class FileSearch{
private byte [] content;
private File mFile;
public FileSearch(File file){
mFile = file;
}
public boolean hasString(String str){
int size = getFileSize(mFile);
content = new byte [size];
loadFile(mFile, content);
String s = new String(content);
return s.contains(str);
}
}
In this code, there is a function hasString in the FileSearch class to determine whether the document contains the specified string. The process is to load mFile into memory first and then make judgments. However, the problem here is that the content is declared as an instance variable, not a local variable. So, after this function returns, the data of the entire file still exists in memory. It is obvious that we no longer need these data in the future, which leads to unreasonable waste of memory.
To avoid memory leaks in this case, we are required to manage our allocated memory with C/C++ memory management thinking. First, it is to clarify the effective scope of the memory object before declaring the object reference. Memory objects that are valid within a function should be declared as local variables, and those with the same life cycle as the class instance should be declared as instance variables... and so on. Second, remember to manually empty the memory object when it is no longer needed.
Memory leak problem in complex data structures
In actual projects, we often use some more complex data structures to cache the data information needed during the program operation. Sometimes, due to the complexity of the data structure, or we have some special needs (for example, as much cache information as possible to improve the running speed of the program, etc.), it is difficult for us to deal with the data in the data structure. Make a clear definition of the life cycle. At this time, we can use a special mechanism in Java to prevent memory leakage.
We have introduced before that Java's GC mechanism is based on the reference mechanism that tracks memory. Before that, the references we used only defined a form of "Object o;". In fact, this is just a default situation in the Java reference mechanism, and there are some other reference methods in addition. By using these special reference mechanisms and combining with the GC mechanism, we can achieve some of the effects we need.
Several reference methods in Java
There are several different ways of citing in Java, namely: strong citation, soft citation, weak citation and virtual citation. Next, we first understand the significance of these citation methods in detail.
Strong quote
The citations used in the content we introduced before were all strong citations, which are the most common citations used. If an object has a strong reference, it is similar to an essential daily necessity, and the garbage collector will never recycle it. When memory space is insufficient, the Java virtual machine would rather throw an OutOfMemoryError error to cause the program to terminate abnormally than recycle objects with strong references to solve the memory problem.
SoftReference
A typical use of the SoftReference class is for memory-sensitive caches. The principle of SoftReference is to ensure that all soft references are cleared before the JVM reports an insufficient memory when keeping a reference to an object. The key point is that the garbage collector may (or may not) release soft-accessible objects at runtime. Whether an object is freed depends on the garbage collector's algorithm and the amount of memory available when the garbage collector is running.
WeakReference
A typical use of the WeakReference class is to normalize mapping (canonicalized mapping). In addition, weak references are also useful for objects with relatively long lifetimes and low recreation overhead. The key point is that if a weakly accessible object is encountered during the garbage collector run, the object referenced by WeakReference will be released. However, note that the garbage collector may have to run multiple times before it can find and release weakly accessible objects.
PhantomReference
The PhantomReference class can only be used to track upcoming collections of referenced objects. Likewise, it can also be used to perform pre-mortem clearing operations. PhantomReference must be used with the ReferenceQueue class. ReferenceQueue is required because it can act as a notification mechanism. When the garbage collector determines that an object is a virtual access object, the PhantomReference object is placed on its ReferenceQueue. Putting the PhantomReference object on the ReferenceQueue is a notification indicating that the object referenced by the PhantomReference object has ended and is available for collection. This allows you to take action just before the memory occupied by the object is recycled. Reference and ReferenceQueue are used in conjunction with ReferenceQueue.
GC, Reference and ReferenceQueue
A. GC cannot delete the memory of objects with strong references.
B. GC found an object memory with only soft references, then:
① The reference field of the SoftReference object is set to null, so that the object no longer refers to the heap object.
② The heap object referenced by SoftReference is declared as finalizable.
③ When the finalize() method of the heap object is run and the memory occupied by the object is released, the SoftReference object is added to its ReferenceQueue (if the latter exists).
C. GC discovers an object memory with only weak references, then:
① The reference field of the WeakReference object is set to null, so that the object no longer refers to the heap object.
② The heap object referenced by WeakReference is declared as finalizable.
③ When the finalize() method of the heap object is run and the memory occupied by the object is released, the WeakReference object is added to its ReferenceQueue (if the latter exists).
D. GC discovers an object memory that only has virtual references, then:
① The heap object referenced by PhantomReference is declared as finalizable.
② PhantomReference is added to its ReferenceQueue before the heap object is released.
The following points are worth noting:
1. GC will not find soft-referenced memory objects in general. Only when the memory is obviously insufficient will the memory of the soft-referenced object be discovered and released.
2. GC's discovery and release of weak references is not immediately. Sometimes it is necessary to repeat GC several times before it discovers and releases the memory objects with weak references.
3. When soft references and weak references are added to ReferenceQueue, the references to real memory have been set to empty, and the relevant memory has been released. When adding virtual reference to ReferenceQueue, memory has not been released yet and can still be accessed.
Through the above introduction, I believe that you have a certain understanding of the Java citation mechanism and the similarities and differences of several citation methods. Just a concept may be too abstract. Let's use an example to demonstrate how to use the Reference mechanism in your code.
The code copy is as follows:
String str = new String( " hello " ); // ①
ReferenceQueue < String > rq = new ReferenceQueue < String > (); // ②
WeakReference < String > wf = new WeakReference < String > (str, rq); // ③
str = null ; // ④Cancel the strong reference of the "hello" object
String str1 = wf.get(); // ⑤If the "hello" object is not recycled, str1 refers to the "hello" object
// If the "hello" object is not recycled, rq.poll() returns null
Reference <? extends String > ref = rq.poll(); // ⑥
In the above code, pay attention to the two places ⑤⑥. If the "hello" object is not recycled wf.get() will return the "hello" string object, rq.poll() will return null; and the "hello" object has been recycled, then wf.get() will return null, rq.poll() returns a Reference object, but there is no reference to the str object in this Reference object (PhantomReference is different from WeakReference and SoftReference).
Joint application of citation mechanism and complex data structures
By understanding the GC mechanism, reference mechanism, and combining with ReferenceQueue, we can implement some complex data types that prevent memory overflow.
For example, SoftReference has the characteristics of building a Cache system, so we can implement a simple cache system in combination with hash tables. This not only ensures that as much information can be cached, but also ensures that the Java virtual machine will not throw OutOfMemoryError due to memory leakage. This caching mechanism is particularly suitable for situations where memory objects have a long life cycle and the time to generate memory objects is relatively long, such as cache list cover images, etc. For some cases where the life cycle is long but the overhead of generating memory objects is not large, using WeakReference can achieve better memory management.
A copy of SoftHashmap's source code is attached. I believe that after reading it, you will have a deeper understanding of the application of the Reference mechanism.
The code copy is as follows:
package com. *** .widget;
// : SoftHashMap.java
import java.util. * ;
import java.lang.ref. * ;
import android.util.Log;
public class SoftHashMap extends AbstractMap {
/** The internal HashMap that will hold the SoftReference. */
private final Map hash = new HashMap();
/** The number of "hard" references to hold internally. */
private final int HARD_SIZE;
/** The FIFO list of hard references, order of last access. */
private final LinkedList hardCache = new LinkedList();
/** Reference queue for cleared SoftReference objects. */
private ReferenceQueue queue = new ReferenceQueue();
// Strong Reference number
public SoftHashMap() { this ( 100 ); }
public SoftHashMap( int hardSize) { HARD_SIZE = hardSize; }
public Object get(Object key) {
Object result = null ;
// We get the SoftReference represented by that key
SoftReference soft_ref = (SoftReference)hash.get(key);
if (soft_ref != null ) {
// From the SoftReference we get the value, which can be
// null if it was not in the map, or it was removed in
// the processQueue() method defined below
result = soft_ref.get();
if (result == null ) {
// If the value has been garbage collected, remove the
// entry from the HashMap.
hash.remove(key);
} else {
// We now add this object to the beginning of the hard
// reference queue. One reference can occur more than
// once, because lookups of the FIFO queue are slow, so
// we don't want to search through it each time to remove
// duplicates.
// keep recent use object in memory
hardCache.addFirst(result);
if (hardCache.size() > HARD_SIZE) {
// Remove the last entry if list longer than HARD_SIZE
hardCache.removeLast();
}
}
}
return result;
}
/** We define our own subclass of SoftReference which contains
Not only the value but also the key to make it easier to find
the entry in the HashMap after it's been garbage collected. */
private static class SoftValue extends SoftReference {
private final Object key; // always make data member final
/** Did you know that an outer class can access private data
members and methods of an inner class? I didn't know that!
I thought it was only the inner class who could access the
outer class's private information. An outer class can also
access private members of an inner class inside its inner
class. */
private SoftValue(Object k, Object key, ReferenceQueue q) {
super (k, q);
this .key = key;
}
}
/** Here we go through the ReferenceQueue and remove garbage
collected SoftValue objects from the HashMap by looking them
up using the SoftValue.key data member. */
public void processQueue() {
SoftValue sv;
while ((sv = (SoftValue)queue.poll()) != null ) {
if (sv.get() == null ) {
Log.e( " processQueue " , " null " );
} else {
Log.e( " processQueue " , " Not null " );
}
hash.remove(sv.key); // we can access private data!
Log.e( " SoftHashMap " , " release " + sv.key);
}
}
/** Here we put the key, value pair into the HashMap using
a SoftValue object. */
public Object put(Object key, Object value) {
processQueue(); // throw out garbage collected values first
Log.e( " SoftHashMap " , " put into " + key);
return hash.put(key, new SoftValue(value, key, queue));
}
public Object remove(Object key) {
processQueue(); // throw out garbage collected values first
return hash.remove(key);
}
public void clear() {
hardCache.clear();
processQueue(); // throw out garbage collected values
hash.clear();
}
public int size() {
processQueue(); // throw out garbage collected values first
return hash.size();
}
public Set entrySet() {
// no, no, you may NOT do that!!! GRRR
throw new UnsupportedOperationException();
}
}