Mi sono opposto a questo problema durante la distribuzione e la distribuzione di un'applicazione Web complessa, e ho pensato di aggiungere una spiegazione e la mia soluzione.
Quando distribuisco un'applicazione su Apache Tomcat, viene creato un nuovo ClassLoader per quell'app. ClassLoader viene quindi utilizzato per caricare tutte le classi dell'applicazione e su undeploy, tutto dovrebbe andare bene. Tuttavia, in realtà non è così semplice.
Una o più delle classi create durante la vita dell'applicazione Web contengono un riferimento statico che, da qualche parte lungo la linea, fa riferimento a ClassLoader. Dato che il riferimento è originariamente statico, nessuna quantità di garbage collection ripulirà questo riferimento: ClassLoader e tutte le classi caricate, restano qui per rimanere.
E dopo un paio di redeploys, incontriamo OutOfMemoryError.
Ora questo è diventato un problema abbastanza serio. Potrei assicurarmi che Tomcat venga riavviato dopo ogni ridistribuzione, ma che rimuove l'intero server, anziché solo l'applicazione da ridistribuire, il che spesso non è fattibile.
Quindi invece ho messo insieme una soluzione in codice, che funziona su Apache Tomcat 6.0. Non ho testato su nessun altro server applicazioni e devo sottolineare che molto probabilmente non funzionerà senza modifiche su nessun altro server applicazioni .
Vorrei anche dire che personalmente odio questo codice e che nessuno dovrebbe usarlo come "soluzione rapida" se il codice esistente può essere modificato per utilizzare i metodi di spegnimento e pulizia corretti . L'unica volta che questo dovrebbe essere usato è se c'è una libreria esterna da cui dipende il tuo codice (Nel mio caso, era un client RADIUS) che non fornisce un mezzo per ripulire i propri riferimenti statici.
Comunque, con il codice. Questo dovrebbe essere chiamato nel punto in cui l'applicazione non è dispiegata, come il metodo di distruzione di un servlet o (l'approccio migliore) il metodo di Contesto distrutto di 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();