View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.util;
20  
21  import java.lang.ref.PhantomReference;
22  import java.lang.ref.ReferenceQueue;
23  import java.lang.ref.WeakReference;
24  import java.util.concurrent.ConcurrentHashMap;
25  import java.util.concurrent.ConcurrentMap;
26  
27  import org.eclipse.jetty.util.component.AbstractLifeCycle;
28  import org.eclipse.jetty.util.log.Log;
29  import org.eclipse.jetty.util.log.Logger;
30  
31  /**
32   * A facility to detect improper usage of resource pools.
33   * <p>
34   * Resource pools usually have a method to acquire a pooled resource
35   * and a method to released it back to the pool.
36   * <p>
37   * To detect if client code acquires a resource but never releases it,
38   * the resource pool can be modified to use a {@link LeakDetector}.
39   * The modified resource pool should call {@link #acquired(Object)} every time
40   * the method to acquire a resource is called, and {@link #released(Object)}
41   * every time the method to release the resource is called.
42   * {@link LeakDetector} keeps track of these resources and invokes method
43   * {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource
44   * has been leaked (that is, acquired but never released).
45   * <p>
46   * To detect whether client code releases a resource without having
47   * acquired it, the resource pool can be modified to check the return value
48   * of {@link #released(Object)}: if false, it means that the resource was
49   * not acquired.
50   * <p>
51   * IMPLEMENTATION NOTES
52   * <p>
53   * This class relies on {@link System#identityHashCode(Object)} to create
54   * a unique id for each resource passed to {@link #acquired(Object)} and
55   * {@link #released(Object)}. {@link System#identityHashCode(Object)} does
56   * not guarantee that it will not generate the same number for different
57   * objects, but in practice the chance of collision is rare.
58   * <p>
59   * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks.
60   * {@link PhantomReference}s are enqueued in their {@link ReferenceQueue}
61   * <em>after</em> they have been garbage collected (differently from
62   * {@link WeakReference}s that are enqueued <em>before</em>).
63   * Since the resource is now garbage collected, {@link LeakDetector} checks
64   * whether it has been released and if not, it reports a leak.
65   * Using {@link PhantomReference}s is better than overriding {@link #finalize()}
66   * and works also in those cases where {@link #finalize()} is not
67   * overridable.
68   *
69   * @param <T> the resource type.
70   */
71  public class LeakDetector<T> extends AbstractLifeCycle implements Runnable
72  {
73      private static final Logger LOG = Log.getLogger(LeakDetector.class);
74  
75      private final ReferenceQueue<T> queue = new ReferenceQueue<>();
76      private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>();
77      private Thread thread;
78  
79      /**
80       * Tracks the resource as been acquired.
81       *
82       * @param resource the resource that has been acquired
83       * @return whether the resource has been tracked
84       * @see #released(Object)
85       */
86      public boolean acquired(T resource)
87      {
88          String id = id(resource);
89          return resources.putIfAbsent(id, new LeakInfo(resource, id)) == null;
90      }
91  
92      /**
93       * Tracks the resource as been released.
94       *
95       * @param resource the resource that has been released
96       * @return whether the resource has been acquired
97       * @see #acquired(Object)
98       */
99      public boolean released(T resource)
100     {
101         String id = id(resource);
102         return resources.remove(id) != null;
103     }
104 
105     /**
106      * Generates a unique ID for the given resource.
107      *
108      * @param resource the resource to generate the unique ID for
109      * @return the unique ID of the given resource
110      */
111     protected String id(T resource)
112     {
113         return String.valueOf(System.identityHashCode(resource));
114     }
115 
116     @Override
117     protected void doStart() throws Exception
118     {
119         super.doStart();
120         thread = new Thread(this, getClass().getSimpleName());
121         thread.setDaemon(true);
122         thread.start();
123     }
124 
125     @Override
126     protected void doStop() throws Exception
127     {
128         super.doStop();
129         thread.interrupt();
130     }
131 
132     @Override
133     public void run()
134     {
135         try
136         {
137             while (isRunning())
138             {
139                 @SuppressWarnings("unchecked")
140                 LeakInfo leakInfo = (LeakInfo)queue.remove();
141                 if (LOG.isDebugEnabled())
142                     LOG.debug("Resource GC'ed: {}", leakInfo);
143                 if (resources.remove(leakInfo.id) != null)
144                     leaked(leakInfo);
145             }
146         }
147         catch (InterruptedException x)
148         {
149             // Exit
150         }
151     }
152 
153     /**
154      * Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked.
155      *
156      * @param leakInfo the information about the leak
157      */
158     protected void leaked(LeakInfo leakInfo)
159     {
160         LOG.warn("Resource leaked: " + leakInfo.description, leakInfo.stackFrames);
161     }
162 
163     /**
164      * Information about the leak of a resource.
165      */
166     public class LeakInfo extends PhantomReference<T>
167     {
168         private final String id;
169         private final String description;
170         private final Throwable stackFrames;
171 
172         private LeakInfo(T referent, String id)
173         {
174             super(referent, queue);
175             this.id = id;
176             this.description = referent.toString();
177             this.stackFrames = new Throwable();
178         }
179 
180         /**
181          * @return the resource description as provided by the resource's {@link Object#toString()} method.
182          */
183         public String getResourceDescription()
184         {
185             return description;
186         }
187 
188         /**
189          * @return a Throwable instance that contains the stack frames at the time of resource acquisition.
190          */
191         public Throwable getStackFrames()
192         {
193             return stackFrames;
194         }
195 
196         @Override
197         public String toString()
198         {
199             return description;
200         }
201     }
202 }