DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

FinalizableReferenceQueue

10.28.2008
| 1172 views |
  • submit to reddit
        FinalizableReferenceQueue which spawns a thread that doesn't strongly reference the library's class loader. If the background thread has a strong reference to the library's class loader, the library is loaded as part of a web app, and you try to dynamically reload that web app, the old instance of the web app will leak.

/*
 * Copyright (C) 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.base;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.PhantomReference;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Starts a background thread that cleans up after reclaimed referents.
 *
 * <p>Most of this code goes to preventing creation of a strong reference
 * from the thread to this class's loader so we can support reloading in
 * application servers.
 *
 * @author Bob Lee
 */
class FinalizableReferenceQueue extends ReferenceQueue<Object> {

  private static final Logger logger
      = Logger.getLogger(FinalizableReferenceQueue.class.getName());

  private static final ReferenceQueue<Object> queue = startFinalizer();

  /**
   * Returns the singleton instance.
   */
  public static ReferenceQueue<Object> getInstance() {
    return queue;
  }

  private static final String FINALIZER_CLASS_NAME
      = "com.google.common.base.Finalizer";

  /**
   * Starts the finalizer thread.
   */
  private static ReferenceQueue<Object> startFinalizer() {
    try {
      ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
      if (systemLoader != null) {
        /*
         * If the class is already in the system class path, don't bother
         * creating a new class loader.
         */
        try {
          if (systemLoader.loadClass(FINALIZER_CLASS_NAME) != null) {
            //return startFinalizerDirectly();
          }
        }
        catch (ClassNotFoundException e) { /* ignore */ }
      }

      // Read Finalizer class bytes from class loader.
      ClassLoader thisLoader = FinalizableReferenceQueue.class.getClassLoader();

      String fileName = "/" + FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
      InputStream in = thisLoader.getResourceAsStream(fileName);
      if (in == null) {
        throw new IOException("Could not find " + fileName + " in class path.");
      }

      // Copy bytes into a byte[].
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int read;
      while ((read = in.read(buffer)) > -1) {
        out.write(buffer, 0, read);
      }
      in.close();
      byte[] finalizerBytes = out.toByteArray();

      /*
       * We use URLClassLoader because it's the only concrete class loader
       * implementation in the JDK. If we used our own ClassLoader subclass,
       * Finalizer would have a reference to our custom class loader instance,
       * which would reference its class, which would reference the application
       * class loader, which we're trying to prevent.
       */
      ClassLoader loader = new URLClassLoader(new URL[0], systemLoader);
      Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",
          String.class, byte[].class, int.class, int.class);
      defineClass.setAccessible(true);
      Class<?> finalizer = (Class<?>) defineClass.invoke(loader,
          FINALIZER_CLASS_NAME, finalizerBytes, 0, finalizerBytes.length);

      // Give Finalizer a reference to this class so it knows when to stop.
      Method backReference = finalizer.getDeclaredMethod(
          "backReference", Class.class);
      backReference.setAccessible(true);
      backReference.invoke(null, FinalizableReferenceQueue.class);

      return extractQueue(finalizer);
    } catch (Exception e) {
      logger.log(Level.WARNING, "Could not load " + FINALIZER_CLASS_NAME
          + " in its own class loader.", e);

      // Fall back to loading Finalizer directly.
      return startFinalizerDirectly();
    }
  }

  /**
   * Loads the finalizer in this class loader and starts it.
   */
  private static ReferenceQueue<Object> startFinalizerDirectly() {
    try {
      return extractQueue(Class.forName(FINALIZER_CLASS_NAME));
    }
    catch (ClassNotFoundException e) {
      throw new AssertionError(e);
    }
  }

  /**
   * Copies a queue reference from Finalizer into this class.
   */
  @SuppressWarnings("unchecked")
  private static ReferenceQueue<Object> extractQueue(Class<?> finalizer) {
    try {
      Field queueField = finalizer.getDeclaredField("queue");
      queueField.setAccessible(true);
      return (ReferenceQueue<Object>) queueField.get(null);
    }
    catch (NoSuchFieldException e) {
      throw new RuntimeException(e);
    }
    catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }
}

/**
 * Thread that finalizes referents. All references should implement Runnable.
 *
 * THE APPLICATION CLASS LOADER SHOULD *NOT* LOAD THIS CLASS DIRECTLY. THIS
 * CLASS AND ITS CLASS LOADER SHOULD NOT STRONGLY REFERENCE THE APPLICATION
 * CLASS LOADER.
 *
 * Doing so would create a strong reference from a thread to an application
 * class which would keep the garbage collector from reclaiming the
 * application class loader, thereby breaking reloading of applications
 * in application servers by leaking memory.
 */
class Finalizer extends Thread {

  private static final Logger logger
      = Logger.getLogger(Finalizer.class.getName());

  /**
   * Reference queue. The application will grab a reference to this
   * reflectively.
   */
  static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();

  private static PhantomReference<Class<?>> backReference;

  /**
   * Creates a reference back to the application class loader so we can find
   * out if it has been garbage collected. Once it has, we can safely stop
   * the finalizer thread.
   */
  static void backReference(Class<?> clazz) {
    backReference = new PhantomReference<Class<?>>(clazz, queue);
  }

  static {
    // Start background thread.
    new Finalizer().start();
  }

  /** Constructs a new finalizer thread. */
  private Finalizer() {
    super(Finalizer.class.getSimpleName());
    setDaemon(true);
  }

  /**
   * Loops continuously, pulling references off the queue and cleaning them up.
   */ 
  @Override
  public void run() {
    while (true) {
      try {
        Reference<?> reference = queue.remove();

        if (reference == backReference) {
          // The application class loader has been garbage collected. We can
          // stop and be collected ourselves.
          // Theoretically, this is the last reference in the queue.
          return;
        }

        cleanUp(reference);
      } catch (InterruptedException e) { /* ignore */ }
    }
  }

  /**
   * Cleans up a single reference. Catches and logs all throwables.
   */
  private void cleanUp(Reference<?> reference) {
    try {
      // TODO: We really don't want to make the FinalizableXxxReference classes
      // implement Runnable (it would expose the finalization in the public
      // API!). We need to pass FinalizableReference.class into this class
      // (perhaps in backReference()) and then reflectively invoke
      // finalizeReferent().
      ((Runnable) reference).run();
    } catch (Throwable t) {
      logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
    }
  }
}