J'ai buté ma tête contre ce problème lors du déploiement et du retrait d'une application Web complexe, et j'ai pensé ajouter une explication et ma solution.
Lorsque je déploie une application sur Apache Tomcat, un nouveau ClassLoader est créé pour cette application. Le ClassLoader est ensuite utilisé pour charger toutes les classes de l'application, et lors du retrait, tout est censé bien disparaître. Cependant, en réalité, ce n'est pas aussi simple.
Une ou plusieurs des classes créées au cours de la vie de l'application Web contiennent une référence statique qui, quelque part le long de la ligne, fait référence au ClassLoader. Comme la référence est à l'origine statique, aucune quantité de récupération de place ne nettoiera cette référence - le ClassLoader et toutes les classes qu'il est chargé sont là pour rester.
Et après quelques redéploiements, nous rencontrons OutOfMemoryError.
Maintenant, c'est devenu un problème assez grave. Je pourrais m'assurer que Tomcat est redémarré après chaque redéploiement, mais cela supprime l'ensemble du serveur, plutôt que simplement l'application redéployée, ce qui n'est souvent pas faisable.
Au lieu de cela, j'ai mis en place une solution dans le code, qui fonctionne sur Apache Tomcat 6.0. Je n'ai testé sur aucun autre serveur d'applications et je dois souligner qu'il est très probable que cela ne fonctionne pas sans modification sur un autre serveur d'applications .
Je voudrais également dire que personnellement, je déteste ce code, et que personne ne devrait l'utiliser comme une «solution rapide» si le code existant peut être modifié pour utiliser des méthodes d'arrêt et de nettoyage appropriées . La seule fois où cela devrait être utilisé, c'est s'il existe une bibliothèque externe dont votre code dépend (dans mon cas, c'était un client RADIUS) qui ne fournit pas un moyen de nettoyer ses propres références statiques.
Quoi qu'il en soit, avec le code. Cela doit être appelé au point où l'application ne se déploie pas - comme la méthode destroy d'un servlet ou (la meilleure approche) la méthode contextDestroyed d'un 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();