2929 * This utility creates a background thread to handle file deletion.
3030 * Each file to be deleted is registered with a handler object.
3131 * When the handler object is garbage collected, the file is deleted.
32+ * <p>
33+ * In an environment with multiple class loaders (a servlet container, for
34+ * example), you should consider stopping the background thread if it is no
35+ * longer needed. This is done by invoking the method
36+ * {@link {@link #exitWhenFinished}, typically in
37+ * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
3238 *
3339 * @author Noel Bergman
3440 * @author Martin Cooper
@@ -39,47 +45,19 @@ public class FileCleaner {
3945 /**
4046 * Queue of <code>Tracker</code> instances being watched.
4147 */
42- private static ReferenceQueue /* Tracker */ q = new ReferenceQueue ();
43-
48+ static ReferenceQueue /* Tracker */ q = new ReferenceQueue ();
4449 /**
4550 * Collection of <code>Tracker</code> instances in existence.
4651 */
47- private static Collection /* Tracker */ trackers = new Vector ();
48-
52+ static Collection /* Tracker */ trackers = new Vector ();
4953 /**
50- * The thread that will clean up registered files .
54+ * Whether to terminate the thread when the tracking is complete .
5155 */
52- private static Thread reaper = new Thread ("File Reaper" ) {
53-
54- /**
55- * Run the reaper thread that will delete files as their associated
56- * marker objects are reclaimed by the garbage collector.
57- */
58- public void run () {
59- for (;;) {
60- Tracker tracker = null ;
61- try {
62- // Wait for a tracker to remove.
63- tracker = (Tracker ) q .remove ();
64- } catch (Exception e ) {
65- continue ;
66- }
67-
68- tracker .delete ();
69- tracker .clear ();
70- trackers .remove (tracker );
71- }
72- }
73- };
74-
56+ static volatile boolean exitWhenFinished = false ;
7557 /**
76- * The static initializer that starts the reaper thread .
58+ * The thread that will clean up registered files .
7759 */
78- static {
79- reaper .setPriority (Thread .MAX_PRIORITY );
80- reaper .setDaemon (true );
81- reaper .start ();
82- }
60+ static Thread reaper ;
8361
8462 //-----------------------------------------------------------------------
8563 /**
@@ -109,7 +87,7 @@ public static void track(File file, Object marker, FileDeleteStrategy deleteStra
10987 if (file == null ) {
11088 throw new NullPointerException ("The file must not be null" );
11189 }
112- trackers . add ( new Tracker ( file .getPath (), deleteStrategy , marker , q ) );
90+ addTracker ( file .getPath (), marker , deleteStrategy );
11391 }
11492
11593 /**
@@ -139,9 +117,28 @@ public static void track(String path, Object marker, FileDeleteStrategy deleteSt
139117 if (path == null ) {
140118 throw new NullPointerException ("The path must not be null" );
141119 }
120+ addTracker (path , marker , deleteStrategy );
121+ }
122+
123+ /**
124+ * Adds a tracker to the list of trackers.
125+ *
126+ * @param path the full path to the file to be tracked, not null
127+ * @param marker the marker object used to track the file, not null
128+ * @param deleteStrategy the strategy to delete the file, null means normal
129+ */
130+ private static synchronized void addTracker (String path , Object marker , FileDeleteStrategy deleteStrategy ) {
131+ if (exitWhenFinished ) {
132+ throw new IllegalStateException ("No new trackers can be added once exitWhenFinished() is called" );
133+ }
134+ if (reaper == null ) {
135+ reaper = new Reaper ();
136+ reaper .start ();
137+ }
142138 trackers .add (new Tracker (path , deleteStrategy , marker , q ));
143139 }
144140
141+ //-----------------------------------------------------------------------
145142 /**
146143 * Retrieve the number of files currently being tracked, and therefore
147144 * awaiting deletion.
@@ -152,11 +149,75 @@ public static int getTrackCount() {
152149 return trackers .size ();
153150 }
154151
152+ /**
153+ * Call this method to cause the file cleaner thread to terminate when
154+ * there are no more objects being tracked for deletion.
155+ * <p>
156+ * In a simple environment, you don't need this method as the file cleaner
157+ * thread will simply exit when the JVM exits. In a more complex environment,
158+ * with multiple class loaders (such as an application server), you should be
159+ * aware that the file cleaner thread will continue running even if the class
160+ * loader it was started from terminates. This can consitute a memory leak.
161+ * <p>
162+ * For example, suppose that you have developed a web application, which
163+ * contains the commons-io jar file in your WEB-INF/lib directory. In other
164+ * words, the FileCleaner class is loaded through the class loader of your
165+ * web application. If the web application is terminated, but the servlet
166+ * container is still running, then the file cleaner thread will still exist,
167+ * posing a memory leak.
168+ * <p>
169+ * This method allows the thread to be terminated. Simply call this method
170+ * in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.
171+ * One called, no new objects can be tracked by the file cleaner.
172+ */
173+ public static synchronized void exitWhenFinished () {
174+ exitWhenFinished = true ;
175+ if (reaper != null ) {
176+ synchronized (reaper ) {
177+ reaper .interrupt ();
178+ }
179+ }
180+ }
181+
182+ // -----------------------------------------------------------------------
183+ /**
184+ * The reaper thread.
185+ */
186+ static final class Reaper extends Thread {
187+ Reaper () {
188+ super ("File Reaper" );
189+ setPriority (Thread .MAX_PRIORITY );
190+ setDaemon (true );
191+ }
192+
193+ /**
194+ * Run the reaper thread that will delete files as their associated
195+ * marker objects are reclaimed by the garbage collector.
196+ */
197+ public void run () {
198+ // thread exits when exitWhenFinished is true and there are no more tracked objects
199+ while (exitWhenFinished == false || trackers .size () > 0 ) {
200+ Tracker tracker = null ;
201+ try {
202+ // Wait for a tracker to remove.
203+ tracker = (Tracker ) q .remove ();
204+ } catch (Exception e ) {
205+ continue ;
206+ }
207+ if (tracker != null ) {
208+ tracker .delete ();
209+ tracker .clear ();
210+ trackers .remove (tracker );
211+ }
212+ }
213+ }
214+ }
215+
155216 //-----------------------------------------------------------------------
156217 /**
157218 * Inner class which acts as the reference for a file pending deletion.
158219 */
159- static class Tracker extends PhantomReference {
220+ static final class Tracker extends PhantomReference {
160221
161222 /**
162223 * The full path to the file being tracked.
0 commit comments