Java Theory and Practice: block memory leakage with weak references

Source: Internet
Author: User
Java Theory and Practice: Use Weak references to block memory leaks-Linux general technology-Linux programming and kernel information. The following is a detailed description. Although Java™In theory, a program written in a language does not have a "memory leak", but sometimes objects are not garbage collected after they are no longer part of the logic state of the program. This month, Brian Goetz, an engineer responsible for ensuring application health, discussed common causes of unintentional object retention and showed how to block leaks with weak references.
The logical lifecycle of an object that is no longer used by the garbage collection (GC) recycler (the time when the application uses it) the actual lifecycle of the reference to this object must be the same. In most cases, good Software Engineering Technology guarantees that this is automatically implemented, so we don't have to worry too much about object lifecycle issues. However, we occasionally create a reference that takes longer time to include objects in the memory than we expected. In this case, it is called the unintentional object retention ).

Memory leakage caused by Global Map

The most common reason for retaining an unconscious object is that the metadata is associated with a temporary object using Map. Assume that an object has a medium life cycle, which is longer than the call life cycle of the method assigned to it, but shorter than the application's life cycle, such as the client's socket connection. You need to associate some metadata with this socket, such as generating the user ID for the connection. You do not know this information when creating a Socket, and you cannot add data to a Socket object because you cannot control the Socket class or its subclass. A typical method is to store the information in a Global Map, as shown in the SocketManager class in Listing 1:

Listing 1. Associate metadata with an object using a Global Map

Public class SocketManager {
Private Map m = new HashMap ();

Public void setUser (Socket s, User u ){
M. put (s, u );
}
Public User getUser (Socket s ){
Return m. get (s );
}
Public void removeUser (Socket s ){
M. remove (s );
}
}

SocketManager socketManager;
...
SocketManager. setUser (socket, user );

The problem with this method is that the life cycle of metadata needs to be linked to the life cycle of the socket, but unless you know exactly when the program no longer needs this socket and remember to delete the corresponding ing from the Map, otherwise, the Socket and User objects will remain in the Map forever, far more than the time for responding to the request and closing the Socket. This prevents Socket and User objects from being garbage collected, even if the application does not use them any more. These objects are left uncontrolled, which can easily cause the program to fill up the memory after a long period of operation. In addition to the simplest cases, it is a very annoying and error-prone task to find out when the Socket is no longer used by the program in almost all cases, and the memory needs to be managed manually.

Find out memory leakage

The first sign of Memory leakage in a program is that it throws an OutOfMemoryError or shows poor performance due to frequent garbage collection. Fortunately, garbage collection can provide a large amount of information that can be used to diagnose memory leaks. If the JVM is called using the-verbose: gc or-Xloggc option, a diagnostic information is printed on the console or in the log file each time the GC is run, this includes the time it took, the current heap usage, and the amount of memory it has recovered. GC usage is not recorded. Therefore, if you need to analyze memory problems or tune the Garbage Collector, it is worthwhile to enable GC logs in the production environment by default.

Some tools can output GC logs and display them graphically. JTune is such a tool (see references ). Observe the heap size chart after GC to see the trend of program memory usage. For most programs, memory usage can be divided into two parts: baseline usage and current load usage. For server applications, baseline usage means the memory usage when the application has no load but is ready to accept the request, current load is used in the process of processing the request, but the memory will be released after the request is processed. As long as the load is basically constant, applications usually quickly reach a stable memory usage level. If the memory usage continues to increase when the application Initialization is complete and the load is not increased, the program may retain the generated object when processing the previous request.

Listing 2 shows a program with Memory leakage. MapLeaker processes tasks in the thread pool and records the status of each task in a Map. Unfortunately, after the task is completed, it will not delete that item, so the state items and task objects (and their internal states) will continue to accumulate.

Listing 2. Programs with Map-based Memory leakage

Public class MapLeaker {
Public ExecutorService exec = Executors. newFixedThreadPool (5 );
Public Map taskStatus
= Collections. synchronizedMap (new HashMap ());
Private Random random = new Random ();

Private enum TaskStatus {NOT_STARTED, STARTED, FINISHED };

Private class Task implements Runnable {
Private int [] numbers = new int [random. nextInt (200)];

Public void run (){
Int [] temp = new int [random. nextInt (10000)];
TaskStatus. put (this, TaskStatus. STARTED );
DoSomeWork ();
TaskStatus. put (this, TaskStatus. FINISHED );
}
}

Public Task newTask (){
Task t = new Task ();
TaskStatus. put (t, TaskStatus. NOT_STARTED );
Exec.exe cute (t );
Return t;
}
}

Figure 1 shows the change of application heap size over time after MapLeaker GC. The increasing trend is the warning signal of Memory leakage. (In real applications, the slope is not that big, but after collecting GC data for a long enough time, the upward trend is usually very obvious .)


Figure 1. Increasing memory usage Trend

After you are sure that there is a memory leak, the next step is to find out which object causes this problem. All memory analyzers can generate heap snapshots decomposed by object classes. There are some good commercial heap analysis tools, but finding out memory leaks doesn't have to spend money on these tools-the built-in hprof tool can do the same. To use hprof and keep it track of memory usage, you must use the-Xrunhprof: heap = sites option to call JVM.

Listing 3 shows the hprof output related to the application memory usage. (Hprof tool is used to generate and use decomposition when the application exits, or when kill-3 or pressing Ctrl + Break in Windows .) Note that compared with the two snapshots, the Map. Entry, Task, and int [] objects increase significantly.

See listing 3.

Listing 4 shows another part of the hprof output and provides the call stack information for the allocation point of the Map. Entry object. This output tells us which call chains generate the Map. Entry object with some program analysis to find out the memory leakage source, which is generally quite easy.

Listing 4. HPROF output, showing the allocation point of the Map. Entry object

TRACE 300446:
Java. util. HashMap $ Entry. (: Unknown line)
Java. util. HashMap. addEntry (: Unknown line)
Java. util. HashMap. put (: Unknown line)
Java. util. Collections $ SynchronizedMap. put (: Unknown line)
Com. quiotix. dummy. MapLeaker. newTask (MapLeaker. java: 48)
Com. quiotix. dummy. MapLeaker. main (MapLeaker. java: 64)

Weak reference to rescue

The problem with SocketManager is that the lifecycle of the Socket-User ing should match the lifecycle of the Socket, but the language does not provide any easy way to implement this rule. This makes the program have to use the old technology of manual memory management. Fortunately, since JDK 1.2, the garbage collector provides a way to declare this object's lifecycle dependency, so that the garbage collector can help us prevent this memory leakage-using weak references.

A weak reference is the holder of a reference to an object (called a referent. When weak references are used, the referent reference can be maintained without being blocked from being collected by garbage collection. When the Garbage Collector traces the heap, if only weak references are referenced to an object, the referent will become a candidate object for garbage collection, just as there is no remaining reference, all remaining weak references are cleared. (Only the objects with weak references are called weakly reachable ).)

The referent of WeakReference is set during construction. You can use get () to obtain its value before being cleared. If the weak reference is cleared (whether the referent has been garbage collected or WeakReference. clear () is called), get () returns null. Correspondingly, before using the results, you should always check whether get () returns a non-null value, because referent will always be garbage collected.

When a common (strong) reference is used to copy an object reference, the lifecycle of the referent is limited to at least the same length as that of the copied reference. If you are not careful, it may be the same as the life cycle of the program-if you put an object into a global set. On the other hand, when creating a weak reference to an object, the lifecycle of the referent is not extended at all, but the method to reach it is maintained when the object is still alive.

Weak references are most useful for constructing weak sets. For example, a set that stores metadata about these objects during the rest of the application uses objects-this is what the SocketManager class does. This is the most common usage of weak references. WeakHashMap is also added to the JDK 1.2 class library, which uses weak references for keys rather than values. If an object is used as the key in a normal HashMap, this object cannot be recycled before the ing is deleted from the Map. WeakHashMap allows you to use an object as the Map key, this object will not be blocked from being garbage collected. Listing 5 shows a possible implementation of WeakHashMap's get () method, which shows the use of weak references:

Listing 5. A possible implementation of WeakReference. get ()

Public class WeakHashMap implements Map {

Private static class Entry extends WeakReference
Implements Map. Entry {
Private V value;
Private final int hash;
Private Entry next;
...
}

Public V get (Object key ){
Int hash = getHash (key );
Entry e = getChain (hash );
While (e! = Null ){
K eKey = e. get ();
If (e. hash = hash & (key = eKey | key. equals (eKey )))
Return e. value;
E = e. next;
}
Return null;
}

Call WeakReference. when get (), it returns a strong reference to the referent (if it is still alive), so there is no need to worry about the ing in the while LOOP body, because a strong reference will prevent it from being garbage collected. The implementation of WeakHashMap shows a common usage of weak references-some internal object extension WeakReference. The reason is explained in the following section when we discuss the reference queue.

When you add a ing to WeakHashMap, remember that the ing may be "detached" in the future because the key is garbage collected. In this case, get () returns null, which makes it more important to test whether the return value of get () is null than usual.

Use WeakHashMap to block Leakage

It is easy to prevent leakage in SocketManager. You only need to use WeakHashMap instead of HashMap, as shown in Listing 6. (If SocketManager requires thread security, you can use Collections. synchronizedMap () to package WeakHashMap ). This method can be used when the ing lifecycle must be associated with the key lifecycle. However, you should be careful not to abuse this technology. In most cases, you should use normal HashMap as the implementation of Map.

Listing 6. Use WeakHashMap to fix SocketManager

Public class SocketManager {
Private Map m = new WeakHashMap ();

Public void setUser (Socket s, User u ){
M. put (s, u );
}
Public User getUser (Socket s ){
Return m. get (s );
}
}

Reference queue

WeakHashMap uses a weak reference to hold the ing key, which makes it possible for applications to collect the key objects without using them. The get () implementation can be implemented according to WeakReference. whether get () returns null to distinguish between dead ing and live ing. However, this only prevents the memory consumption of Map from increasing the half of the work required during the application lifecycle, you also need to do some work to delete dead items from Map after the key object is collected. Otherwise, Map will be filled with items corresponding to the dead key. Although this is invisible to the application, it still causes the application to run out of memory, because even if the key is collected, the Map. Entry and value objects are not collected.

You can periodically scan the Map, call get () for each weak reference, and delete the ing when get () returns null to eliminate the dead ing. However, if Map has many active items, the efficiency of this method is very low. If there is a way to send a notification when the weak referenced referent is collected by garbage collection, this is the role of the reference queue.

The reference queue is the primary method for the garbage collector to return information about the object lifecycle to the application. Weak references have two constructor functions: one takes only the referent as the parameter, and the other takes the reference queue as the parameter. If a weak reference is created using an associated reference queue, when the referent becomes a GC candidate object, the referenced object (not a referent) is added to the reference queue after the reference is cleared. Then, the application extracts the reference from the reference queue and learns that its referent has been collected. Therefore, you can perform corresponding cleanup activities, such as removing the items of objects that are no longer in the weak set. (The reference queue provides the same out-of-column mode as BlockingQueue-polled, timed blocking, and untimed blocking .)

WeakHashMap has a private method named expungeStaleEntries (). It is called in most Map operations. It removes all invalid references in the reference queue and deletes the associated mappings. Listing 7 shows a possible implementation of expungeStaleEntries. The Entry type used to store key-value ing extends WeakReference. Therefore, when expungeStaleEntries () requires the next invalid weak reference, it obtains an Entry. It is more effective to use the reference queue instead of the regular content scanning method to clear the Map, because the cleanup process does not touch the active items, and it only works when there is a reference actually added to the queue.

Listing 7. Possible implementations of WeakHashMap. expungeStaleEntries ()

Private void expungeStaleEntries (){
Entry e;
While (e = (Entry) queue. poll ())! = Null ){
Int hash = e. hash;

Entry prev = getChain (hash );
Entry cur = prev;
While (cur! = Null ){
Entry next = cur. next;
If (cur = e ){
If (prev = e)
SetChain (hash, next );
Else
Prev. next = next;
Break;
}
Prev = cur;
Cur = next;
}
}
}
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.