Ich habe mich beim Bereitstellen und Aufheben der Bereitstellung einer komplexen Webanwendung mit diesem Problem auseinandergesetzt und dachte, ich würde eine Erklärung und meine Lösung hinzufügen.
Wenn ich eine Anwendung auf Apache Tomcat bereitstelle, wird ein neuer ClassLoader für diese App erstellt. Der ClassLoader wird dann verwendet, um alle Klassen der Anwendung zu laden, und beim Aufheben der Bereitstellung sollte alles gut gehen. In Wirklichkeit ist es jedoch nicht ganz so einfach.
Eine oder mehrere der Klassen, die während der Lebensdauer der Webanwendung erstellt wurden, enthalten eine statische Referenz, die irgendwo entlang der Linie auf den ClassLoader verweist. Da die Referenz ursprünglich statisch ist, wird diese Referenz durch keine Speicherbereinigung bereinigt. Der ClassLoader und alle geladenen Klassen bleiben erhalten.
Und nach ein paar Neueinsätzen stoßen wir auf den OutOfMemoryError.
Nun ist dies ein ziemlich ernstes Problem geworden. Ich könnte sicherstellen, dass Tomcat nach jeder erneuten Bereitstellung neu gestartet wird, aber dadurch wird der gesamte Server heruntergefahren und nicht nur die neu bereitgestellte Anwendung, was häufig nicht möglich ist.
Also habe ich stattdessen eine Lösung in Code zusammengestellt, die unter Apache Tomcat 6.0 funktioniert. Ich habe auf keinem anderen Anwendungsserver getestet und muss betonen, dass dies ohne Änderungen auf einem anderen Anwendungsserver sehr wahrscheinlich nicht funktioniert .
Ich möchte auch sagen, dass ich diesen Code persönlich hasse und dass niemand dies als "schnelle Lösung" verwenden sollte, wenn der vorhandene Code geändert werden kann, um die richtigen Methoden zum Herunterfahren und Bereinigen zu verwenden . Dies sollte nur verwendet werden, wenn es eine externe Bibliothek gibt, von der Ihr Code abhängig ist (in meinem Fall war es ein RADIUS-Client), die keine Möglichkeit bietet, seine eigenen statischen Referenzen zu bereinigen.
Wie auch immer, weiter mit dem Code. Dies sollte an dem Punkt aufgerufen werden, an dem die Anwendung nicht bereitgestellt wird, z. B. die Zerstörungsmethode eines Servlets oder (der bessere Ansatz) die contextDestroyed-Methode eines 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();