1 // 2 // ======================================================================== 3 // Copyright (c) 1995-2015 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 and a method to released it back to the pool. 35 * <p> 36 * To detect if client code acquires a resource but never releases it, the resource pool can be modified to use a 37 * {@link LeakDetector}. The modified resource pool should call {@link #acquired(Object)} every time the method to 38 * acquire a resource is called, and {@link #released(Object)} every time the method to release the resource is called. 39 * {@link LeakDetector} keeps track of these resources and invokes method 40 * {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource has been leaked (that 41 * is, acquired but never released). 42 * <p> 43 * To detect whether client code releases a resource without having acquired it, the resource pool can be modified to 44 * check the return value of {@link #released(Object)}: if false, it means that the resource was not acquired. 45 * <p> 46 * IMPLEMENTATION NOTES 47 * <p> 48 * This class relies on {@link System#identityHashCode(Object)} to create a unique id for each resource passed to 49 * {@link #acquired(Object)} and {@link #released(Object)}. {@link System#identityHashCode(Object)} does not guarantee 50 * that it will not generate the same number for different objects, but in practice the chance of collision is rare. 51 * <p> 52 * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks. {@link PhantomReference}s are enqueued in their 53 * {@link ReferenceQueue} <em>after</em> they have been garbage collected (differently from {@link WeakReference}s that 54 * are enqueued <em>before</em>). Since the resource is now garbage collected, {@link LeakDetector} checks whether it 55 * has been released and if not, it reports a leak. Using {@link PhantomReference}s is better than overriding 56 * {@link #finalize()} and works also in those cases where {@link #finalize()} is not overridable. 57 * 58 * @param <T> the resource type. 59 */ 60 public class LeakDetector<T> extends AbstractLifeCycle implements Runnable 61 { 62 private static final Logger LOG = Log.getLogger(LeakDetector.class); 63 64 private final ReferenceQueue<T> queue = new ReferenceQueue<>(); 65 private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>(); 66 private Thread thread; 67 68 /** 69 * Tracks the resource as been acquired. 70 * 71 * @param resource the resource that has been acquired 72 * @return true whether the resource has been acquired normally, false if the resource has detected a leak (meaning 73 * that another acquire occurred before a release of the same resource) 74 * @see #released(Object) 75 */ 76 public boolean acquired(T resource) 77 { 78 String id = id(resource); 79 LeakInfo info = resources.putIfAbsent(id, new LeakInfo(resource,id)); 80 if (info != null) 81 { 82 // Leak detected, prior acquire exists (not released) or id clash. 83 return false; 84 } 85 // Normal behavior. 86 return true; 87 } 88 89 /** 90 * Tracks the resource as been released. 91 * 92 * @param resource the resource that has been released 93 * @return true whether the resource has been released normally (based on a previous acquire). false if the resource 94 * has been released without a prior acquire (such as a double release scenario) 95 * @see #acquired(Object) 96 */ 97 public boolean released(T resource) 98 { 99 String id = id(resource); 100 LeakInfo info = resources.remove(id); 101 if (info != null) 102 { 103 // Normal behavior. 104 return true; 105 } 106 107 // Leak detected (released without acquire). 108 return false; 109 } 110 111 /** 112 * Generates a unique ID for the given resource. 113 * 114 * @param resource the resource to generate the unique ID for 115 * @return the unique ID of the given resource 116 */ 117 public String id(T resource) 118 { 119 return String.valueOf(System.identityHashCode(resource)); 120 } 121 122 @Override 123 protected void doStart() throws Exception 124 { 125 super.doStart(); 126 thread = new Thread(this,getClass().getSimpleName()); 127 thread.setDaemon(true); 128 thread.start(); 129 } 130 131 @Override 132 protected void doStop() throws Exception 133 { 134 super.doStop(); 135 thread.interrupt(); 136 } 137 138 @Override 139 public void run() 140 { 141 try 142 { 143 while (isRunning()) 144 { 145 @SuppressWarnings("unchecked") 146 LeakInfo leakInfo = (LeakInfo)queue.remove(); 147 if (LOG.isDebugEnabled()) 148 LOG.debug("Resource GC'ed: {}",leakInfo); 149 if (resources.remove(leakInfo.id) != null) 150 leaked(leakInfo); 151 } 152 } 153 catch (InterruptedException x) 154 { 155 // Exit 156 } 157 } 158 159 /** 160 * Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked. 161 * 162 * @param leakInfo the information about the leak 163 */ 164 protected void leaked(LeakInfo leakInfo) 165 { 166 LOG.warn("Resource leaked: " + leakInfo.description,leakInfo.stackFrames); 167 } 168 169 /** 170 * Information about the leak of a resource. 171 */ 172 public class LeakInfo extends PhantomReference<T> 173 { 174 private final String id; 175 private final String description; 176 private final Throwable stackFrames; 177 178 private LeakInfo(T referent, String id) 179 { 180 super(referent,queue); 181 this.id = id; 182 this.description = referent.toString(); 183 this.stackFrames = new Throwable(); 184 } 185 186 /** 187 * @return the resource description as provided by the resource's {@link Object#toString()} method. 188 */ 189 public String getResourceDescription() 190 { 191 return description; 192 } 193 194 /** 195 * @return a Throwable instance that contains the stack frames at the time of resource acquisition. 196 */ 197 public Throwable getStackFrames() 198 { 199 return stackFrames; 200 } 201 202 @Override 203 public String toString() 204 { 205 return description; 206 } 207 } 208 }