For garbage collection (GC) to reclaim objects no longer in use by the program, the logical lifetime of an object (the time that the application will use it) and the actual lifetime of outstanding references to that object must be the same. Most of the time, good software engineering techniques ensure that this happens automatically, without us having to expend a lot of attention on the issue of object lifetime. But every once in a while, we create a reference that holds an object in memory much longer than we expected it to, a situation referred to as unintentional object retention.
The most common source of unintentional object retention is the use of a Map
to associate metadata with transient objects. Let‘s say you have an object with an intermediate lifetime -- longer than that of the method call that allocated it, but shorter than that of the application -- such as a socket connection from a client. You want to associate some metadata with that socket, such as the identity of the user that has made the connection. You don‘t know this information at the time the Socket
is created, and you cannot add data to the Socket
object because you do not control the Socket
class or its instantiation. In this case, the typical approach is to store such information in a global Map
, as shown in the SocketManager
class in Listing 1:
理解:当我们使用Map来将元数据和临时对象联系到一起的时候,无意间会产生对象滞留。假如说你有一个对象,比如socket连接,他的生命周期比创建它的函数要长,但比应用的时间短。当你想要将一些元数据和这个socket关联起来,比如创建这个套接字的用户。由于你不能控制socket类和socket实例(理解:为socket类添加用户信息的属性),所以只能够通过全局Map的方式来进行解决,比如SocketManager:
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 lifetime of the metadata needs to be tied to the lifetime of the socket, but unless you know exactly when the socket is no longer needed by the program and remember to remove the corresponding mapping from the Map
, the Socket
and User
objects will stay in the Map
forever, long after the request has been serviced and the socket has been closed. This prevents the Socket
and User
objects from being garbage collected, even though the application is never going to use either of them again. Left unchecked, this could easily cause a program to run out of memory if it runs for long enough. In all but the most trivial cases, the techniques for identifying when the Socket
becomes no longer used by the program resemble the annoying and error-prone techniques required for manual memory management.
理解:由于元数据需要和socket的生命周期联系到一起,除非你能准确的知道这个socket不再别程序需要的时间,并将其移除(移除socket和对应的元数据),否则在这个socket服务完关闭后,也会一直停留在map中。这种方案会导致socket和对应的user对象无法被回收,虽然它们永远不会再被应用使用。如果不主动检查内存的话,在程序运行一段时间后会出现内存泄露。让程序员来确认socket的周期很容易出错。
The first sign that your program has a memory leak is usually that it throws an OutOfMemoryError
or starts to exhibit poor performance because of frequent garbage collection. Fortunately, the garbage collector is willing to share a lot of information that can be used to diagnose a memory leak. If you invoke the JVM with the -verbose:gc
or the -Xloggc
option, a diagnostic message is printed on the console or to a log file every time the GC runs, including how long it took, the current heap usage, and how much memory was recovered. Logging GC usage is not intrusive, and so it is reasonable to enable GC logging in production by default in the event you ever need to analyze memory problems or tune the garbage collector.
Tools can take the GC log output and display it graphically; one such tool is the free JTune (see Resources). By looking at the graph of heap size after GC, you can see the trend of your program‘s memory usage. For most programs, you can divide memory usage into two components: thebaseline usage and the current load usage. For a server application, the baseline usage is what the application uses when it is not subjected to any load but is ready to accept requests; the current load usage is what is used in the process of handling requests but released when the request processing is complete. So long as load is roughly constant, applications typically reach a steady state level of memory usage fairly quickly. If memory usage continues to trend upwards even though the application has completed its initialization and its load is not increasing, the program is probably retaining objects generated in the course of processing prior requests.
Listing 2 shows a program that has a memory leak. MapLeaker
processes tasks in a thread pool and records the status of each task in a Map
. Unfortunately, it never removes the entry when the task is finished, so the status entries and the task objects (along with their internal state) accumulate forever.
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(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.execute(t); return t; } }
Figure 1 shows a graph of the application heap size after GC for MapLeaker
over time. The upward-sloping trend is a telltale sign of a memory leak. (In real applications, the slope is never this dramatic, but it usually becomes apparent if you gather GC data for long enough.)
Once you are convinced you have a memory leak, the next step is to find out what type of objects are causing the problem. Any memory profiler can produce snapshots of the heap broken down by object class. There are some excellent commercial heap profiling tools available, but you don‘t have to spend any money to find memory leaks -- the built-in hprof
tool can also do the trick. To use hprof
and instruct it to track memory usage, invoke the JVM with the -Xrunhprof:heap=sites
option.
Listing 3 shows the relevant portion of the hprof
output breaking down the application‘s memory usage. (The hprof
tool produces a usage breakdown after the application exits, or when you signal your application with a kill -3
or by pressing Ctrl+Break on Windows.) Notice that there has been significant growth in Map.Entry
, Task
, and int[]
objects between the two snapshots.
See Listing 3.
Listing 4 shows yet another portion of the hprof
output, giving call stack information for allocation sites for Map.Entry
objects. This output tells us which call chains are generating the Map.Entry
objects; with some program analysis, it is usually fairly easy to pinpoint the source of the memory leak.
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)
Java theory and practice: Plugging memory leaks with weak references
原文:http://www.cnblogs.com/Guoyutian/p/5071756.html