To make garbage collection (GC) recycle an object that no longer uses, the actual life cycle of the object's logical life cycle (the time the application uses it) and the reference to that object must be the same. Most of the time, good software engineering ensures that this is automated, without having to spend too much thought on the object life cycle issues. But occasionally we create a reference that contains objects in memory much longer than we expected, a condition known as unconscious object retention (unintentional object retention).
Memory leaks due to global Map
The most common cause of unconscious object retention is the use of Map
associating metadata with temporary objects (transient object). Assume that an object has a medium life cycle that is longer than the lifetime of the method call that is assigned it, but is shorter than the life cycle of the application, such as a socket connection for the client. Some meta data needs to be associated with this socket, such as the identity of the user who generated the connection. Socket
This information is not known at the time of creation, and data cannot be added to the Socket
object because Socket
the class or its subclasses cannot be controlled. At this point, the typical approach is to Map
store this information in a global, as shown in the class in Listing 1 SocketManager
:
Listing 1. Associating metadata to an object using a global Map
public class Socketmanager { private map<socket,user> m = new hashmap<socket,user> (); 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 approach is that the lifecycle of the metadata needs to be hooked up to the life cycle of the socket, but unless you know exactly when the program no longer needs the socket, and remember to Map
remove the corresponding mapping from, otherwise, Socket
and the User
object will remain in forever Map
, Far more than the time it took to respond to the request and close the socket. This prevents Socket
and User
objects from being garbage collected, even if the application no longer uses them. These objects are left out of control and can easily cause the program to run out of memory after a long period of time. In almost all cases, it is a tedious and error-prone task to find out when it is no longer used by the program, except in the simplest case, Socket
requiring manual memory management.
Back to top of page
Identify memory leaks
The first sign that a program has a memory leak is usually that it throws one OutOfMemoryError
, or that it shows bad performance because of frequent garbage collection. Fortunately, garbage collection can provide a lot of information that can be used to diagnose memory leaks. If you -verbose:gc
invoke the JVM as an -Xloggc
option, each time the GC runs, a diagnostic message is printed on the console or in the log file, including the time it took, the current heap usage, and how much memory was restored. Logging GC usage is not disruptive, so if you need to analyze memory issues or tune the garbage collector, it is worthwhile to enable GC logging by default in a production environment.
There are tools that can take advantage of GC log output and graphically display it, JTune is one such tool (see Resources). After observing the graph of heap size after GC, you can see the trend of program memory usage. For most programs, memory usage can be divided into two parts:baseline Use and current load . For server applications, baseline use is the memory used by the application without any load, but is ready to accept the request, and current load uses the memory that is used during the processing of the request, but freed after the request processing is complete. As long as the load is largely constant, the application will usually quickly reach a stable level of memory usage. If the memory usage continues to increase when the application has completed its initialization and the load does not increase, the program may retain the generated object while processing the previous request.
Listing 2 shows a program with a memory leak. MapLeaker
processes tasks in a thread pool and Map
records the status of each task in one. Unfortunately, it does not delete the item after the task is completed, so the State and Task objects (and their internal state) accumulate continuously.
Listing 2. Programs with MAP-based memory leaks
public class Mapleaker {public executorservice exec = Executors.newfixedthreadpool (5); Public Map<task, taskstatus> taskstatus = Collections.synchronizedmap (New Hashmap<task, TaskStatus> ( )); Private random random = new random (); Private enum TaskStatus {not_started, STARTED, finished}; Private class Task implements Runnable { private int[] numbers = new Int[random.nextint ($)]; 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.execute (t); return t; }}
Figure 1 shows MapLeaker
a graph of the application heap size changes over time after GC. The uptrend is a warning signal that there is a memory leak. (in real-world applications, the slope is not that big, but after collecting enough GC data for a long time, the uptrend is usually obvious.) )
Figure 1. Persistently rising memory usage trends
When you are sure that you have a memory leak, the next step is to find out which object is causing the problem. All memory analyzers can generate a heap snapshot that is decomposed by the object class. There are some good business heap analysis tools, but finding a memory leak doesn't have to be the money to buy these tools-built-in hprof
tools can do the job. To use hprof
and let it track memory usage, you need to -Xrunhprof:heap=sites
invoke the JVM with options.
Listing 3 shows the relevant parts of the output that exploded the application memory usage hprof
. ( hprof
the tool generates use decomposition when the application exits, or kill -3
when you press Ctrl+break in Windows.) Note that the Map.Entry
, Task
and int[]
objects have increased significantly compared to the two snapshots.
See Listing 3.
Listing 4 shows hprof
another part of the output, giving the Map.Entry
call stack information for the object's allocation point. This output tells us which call chain generated the Map.Entry
object, and with some program analysis, it is generally fairly easy to find the source of the memory leak.
Listing 4. HPROF output, showing the distribution point of the Map.entry object
TRACE 300446:java.util.hashmap$entry.<init> (<unknown source>:unknown line) Java.util.HashMap.addEntry ( <unknown Source>:unknown Line) java.util.HashMap.put (<unknown Source>:unknown line) Java.util.collections$synchronizedmap.put (<unknown Source>:unknown line) Com.quiotix.dummy.MapLeaker.newTask (mapleaker.java:48) com.quiotix.dummy.MapLeaker.main (mapleaker.java:64)
Back to top of page
Weak references to rescue the
SocketManager
The problem is Socket
-the User
life cycle of the mapping should Socket
match the life cycle, but the language does not provide any easy way to implement this rule. This makes the program have to use the old technology of artificial memory management. Fortunately, starting with JDK 1.2, the garbage collector provides a way to declare the life-cycle dependency of this object so that the garbage collector can help us prevent this memory leak-the use of weak references .
A weak reference is the holder of a reference to an object (called a referent). With a weak reference, you can maintain a reference to referent without preventing it from being garbage collected. When the garbage collector tracks the heap, if a reference to an object has only a weak reference, then the referent becomes a candidate for garbage collection, as if there were no remaining references, and all remaining weak references are cleared . (Only weakly referenced objects are called weakly accessible (weakly reachable). )
WeakReference
The referent is set at construction time and can be used to get()
get its value before it is cleared. If the weak reference is cleared (whether the referent has been garbage collected or someone has called it WeakReference.clear()
), it get()
will be returned null
. Accordingly, you should always check get()
whether a non-null value is returned before using its results, because referent is always garbage collected.
When copying an object reference with a normal (strong) reference, the limit referent's lifetime is at least as long as the lifetime of the reference being copied. If you are not careful, it may be the same as the life cycle of the program-if you put an object in a global collection. On the other hand, when creating a weak reference to an object, there is no extension of the life cycle of the referent, except that it retains another way to reach it when the object is still alive .
Weak references are most useful for constructing weak collections, such as those that store metadata about these objects during the remainder of the application's use of objects-this is what SocketManager
the class does. Because this is the most common use of weak references, WeakHashMap
It is also added to the class library of JDK 1.2, which uses weak references to keys rather than to values. If HashMap
you use an object as a key in a normal, then this object Map
cannot be reclaimed until the map is removed from, WeakHashMap
allowing you to use an object as a Map
key without preventing the object from being garbage collected. Listing 5 WeakHashMap
get()
shows a possible implementation of the method, which demonstrates the use of weak references:
Listing 5. A possible implementation of Weakreference.get ()
public class Weakhashmap<k,v> implements map<k,v> { private static class Entry<k,v> extends Weakreference<k> implements map.entry<k,v> { private V value; private final int hash; Private entry<k,v> Next; ... } Public V get (Object key) { int hash = Gethash (key); Entry<k,v> 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; }
WeakReference.get()
when called, it returns a strong reference to the referent (if it still survives), so there is no need to worry about the mapping disappearing in the while
loop body, because a strong reference prevents it from being garbage collected. WeakHashMap
implementation shows a common use of weak references-some internal object extensions WeakReference
. The reason for this is explained in the following section, where reference queues are discussed.
WeakHashMap
when adding mappings to, keep in mind that the mappings may be "detached" later because the keys are garbage collected. In this case, get()
return null
, which makes the test get()
return value null
more important than usual.
Plugging the leak with Weakhashmap
SocketManager
It is easy to prevent leaks in, as long as the WeakHashMap
substitution HashMap
is done, as shown in Listing 6. (If SocketManager
thread safety is required, it can be Collections.synchronizedMap()
wrapped WeakHashMap
). You can use this method when the life cycle of the map must be associated with the life cycle of the key. However, care should be taken not to misuse the technology, and most of the time it should be done with ordinary HashMap
Map
implementations.
Listing 6. Repairing Socketmanager with Weakhashmap
public class Socketmanager { private map<socket,user> m = new weakhashmap<socket,user> (); public void SetUser (Socket s, User u) { M.put (S, u); } Public User GetUser (Socket s) { return m.get (s); }}
Reference queue
WeakHashMap
Using weak references to host mapping keys, which allows applications to no longer use key objects when they can be garbage collected, get()
implementations can WeakReference.get()
null
differentiate between dead mappings and live mappings based on whether they are returned. But this is only Map
half the effort to prevent memory consumption from increasing in the lifetime of the application, and some work needs to be done to remove the dead items from the key objects after they are collected Map
. Otherwise, the Map
item that corresponds to the dead key is filled. While this is not visible to the application, it still causes the application to run out of memory, because even if the key is collected, Map.Entry
and the value object is not collected.
You can eliminate the dead mappings by periodically scanning Map
, invoking each weak reference get()
, and null
deleting the map when get () returns. But if Map
there are many live items, then this method is inefficient. If there is a way to make a notification when a weakly referenced referent is garbage collected, this is the role of the reference queue .
A reference queue is the primary method by which the garbage collector returns information about the object's life cycle to the application. A weak reference has two constructors: one takes referent as a parameter, and the other takes a reference queue as a parameter. If you create a weak reference with the associated reference queue, when referent becomes a GC candidate, the Reference object (not referent) is added to the reference queue after the reference is cleared. After that, the application extracts the reference from the reference queue and understands that its referent has been collected, so it can make the appropriate cleanup activities, such as removing items from objects that are not already in the weak collection. (The reference queue provides BlockingQueue
--polled, timed blocking, and untimed blocking with the same dequeue mode.) )
WeakHashMap
There is a private method named, which expungeStaleEntries()
Map
is called in most operations, which removes all invalid references in the reference queue and deletes the associated mappings. expungeStaleEntries()
a possible implementation is shown in Listing 7. The type used to store the key-value mappings is Entry
extended WeakReference
, so when the expungeStaleEntries()
next invalid weak reference is requested, it gets one Entry
. It is more efficient to use a reference queue instead of a regular scan of the content, Map
because the cleanup process does not touch the live item, it only works if there is a reference to the actual join queue.
Listing 7. Possible implementations of Weakhashmap.expungestaleentries ()
private void Expungestaleentries () {entry<k,v> e; while ((E = (entry<k,v>) queue.poll ()) = null) { int hash = E.hash; Entry<k,v> prev = GetChain (hash); entry<k,v> cur = prev; while (cur! = null) { entry<k,v> next = cur.next; if (cur = = e) { if (prev = = e) setchain (hash, next); else prev.next = next; break; } prev = cur; cur = Next;}}}
Back to top of page
Conclusion
Weak references and weak collections are powerful tools for managing heaps, allowing applications to use more complex accessibility scenarios, not just "either all or none" provided by ordinary (strong) references. Next month, we will analyze soft references related to weak references, and will analyze the behavior of the garbage collector when using weak references and soft references.
Original: http://www.ibm.com/developerworks/cn/java/j-jtp11225/
Java theory and Practice: blocking memory leaks with weak references---reprint