Me he estado enfrentando a este problema mientras desplegaba y desplegaba una aplicación web compleja también, y pensé en agregar una explicación y mi solución.
Cuando implemento una aplicación en Apache Tomcat, se crea un nuevo ClassLoader para esa aplicación. El ClassLoader se usa para cargar todas las clases de la aplicación, y en la anulación de la implementación, se supone que todo desaparecerá bien. Sin embargo, en realidad no es tan simple.
Una o más de las clases creadas durante la vida de la aplicación web contienen una referencia estática que, en algún lugar a lo largo de la línea, hace referencia al ClassLoader. Como la referencia es originalmente estática, ninguna cantidad de recolección de basura limpiará esta referencia: el ClassLoader y todas las clases cargadas están aquí para quedarse.
Y después de un par de redespliegues, nos encontramos con OutOfMemoryError.
Ahora esto se ha convertido en un problema bastante serio. Podría asegurarme de que Tomcat se reinicie después de cada nueva implementación, pero eso elimina todo el servidor, en lugar de solo la aplicación que se vuelve a implementar, lo que a menudo no es factible.
Entonces, en su lugar, armé una solución en código, que funciona en Apache Tomcat 6.0. No he probado en ningún otro servidor de aplicaciones, y debo enfatizar que es muy probable que esto no funcione sin modificaciones en ningún otro servidor de aplicaciones .
También me gustaría decir que personalmente odio este código, y que nadie debería usarlo como una "solución rápida" si el código existente se puede cambiar para usar los métodos adecuados de apagado y limpieza . El único momento en que esto debería usarse es si hay una biblioteca externa de la que depende su código (en mi caso, era un cliente RADIUS) que no proporciona un medio para limpiar sus propias referencias estáticas.
De todos modos, adelante con el código. Esto debería llamarse en el punto donde la aplicación se está desplegando, como el método de destrucción de un servlet o (el mejor enfoque) el método contextDestroyed de ServletContextListener.
//Get a list of all classes loaded by the current webapp classloader
WebappClassLoader classLoader = (WebappClassLoader) getClass().getClassLoader();
Field classLoaderClassesField = null;
Class clazz = WebappClassLoader.class;
while (classLoaderClassesField == null && clazz != null) {
try {
classLoaderClassesField = clazz.getDeclaredField("classes");
} catch (Exception exception) {
//do nothing
}
clazz = clazz.getSuperclass();
}
classLoaderClassesField.setAccessible(true);
List classes = new ArrayList((Vector)classLoaderClassesField.get(classLoader));
for (Object o : classes) {
Class c = (Class)o;
//Make sure you identify only the packages that are holding references to the classloader.
//Allowing this code to clear all static references will result in all sorts
//of horrible things (like java segfaulting).
if (c.getName().startsWith("com.whatever")) {
//Kill any static references within all these classes.
for (Field f : c.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers())
&& !Modifier.isFinal(f.getModifiers())
&& !f.getType().isPrimitive()) {
try {
f.setAccessible(true);
f.set(null, null);
} catch (Exception exception) {
//Log the exception
}
}
}
}
}
classes.clear();