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 }