Michael Bedward ha scritto:
I'm following this with interest and am +0 about it so far because I
don't do web apps.
Regarding JAI, tthere is a method to unregister an operation
descriptor but I've never tried it. Simone: have you any experience
with this ?
On GS devel there is a patch I provided that uses exactly that
(attached, it's a bit messy but you should see the JAI cleanup
spots)
What I'm trying to do here is to donate that back to GeoTools.
Cheers
Andrea
--
Andrea Aime
OpenGeo - http://opengeo.org
Expert service straight from the developers.
diff --git a/src/extension/ogr/src/main/java/org/geoserver/wfs/response/Ogr2OgrConfigurator.java b/src/extension/ogr/src/main/java/org/geoserver/wfs/response/Ogr2OgrConfigurator.java
index 0eadbf3..c49cb9e 100644
--- a/src/extension/ogr/src/main/java/org/geoserver/wfs/response/Ogr2OgrConfigurator.java
+++ b/src/extension/ogr/src/main/java/org/geoserver/wfs/response/Ogr2OgrConfigurator.java
@@ -13,6 +13,9 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.util.logging.Logging;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
import org.vfny.geoserver.global.GeoserverDataDirectory;
import com.thoughtworks.xstream.XStream;
@@ -23,7 +26,7 @@ import com.thoughtworks.xstream.XStream;
* @author Administrator
*
*/
-public class Ogr2OgrConfigurator {
+public class Ogr2OgrConfigurator implements ApplicationListener {
private static final Logger LOGGER = Logging.getLogger(Ogr2OgrConfigurator.class);
Ogr2OgrOutputFormat of;
@@ -108,4 +111,13 @@ public class Ogr2OgrConfigurator {
}
}
+ /**
+ * Kill all threads on web app context shutdown to avoid permgen leaks
+ */
+ public void onApplicationEvent(ApplicationEvent event) {
+ if(event instanceof ContextClosedEvent) {
+ timer.cancel();
+ }
+ }
+
}
diff --git a/src/web/app/src/main/java/org/geoserver/GeoserverInitStartupListener.java b/src/web/app/src/main/java/org/geoserver/GeoserverInitStartupListener.java
index 9d359a2..91d5d58 100644
--- a/src/web/app/src/main/java/org/geoserver/GeoserverInitStartupListener.java
+++ b/src/web/app/src/main/java/org/geoserver/GeoserverInitStartupListener.java
@@ -4,16 +4,45 @@
*/
package org.geoserver;
+import java.beans.Introspector;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Formattable;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.IIOServiceProvider;
import javax.imageio.spi.ImageReaderSpi;
+import javax.media.jai.JAI;
+import javax.media.jai.OperationRegistry;
+import javax.media.jai.RegistryElementDescriptor;
+import javax.media.jai.RegistryMode;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
+import org.apache.commons.logging.LogFactory;
+import org.apache.log4j.LogManager;
+import org.geoserver.logging.LoggingUtils;
+import org.geoserver.platform.GeoServerExtensions;
import org.geotools.factory.Hints;
+import org.geotools.referencing.ReferencingFactoryFinder;
+import org.geotools.referencing.factory.AbstractAuthorityFactory;
+import org.geotools.referencing.factory.DeferredAuthorityFactory;
+import org.geotools.referencing.operation.DefaultMathTransformFactory;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.util.WeakCollectionCleaner;
import org.geotools.util.logging.Logging;
+import org.opengis.referencing.AuthorityFactory;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.MathTransformFactory;
/**
* Listens for GeoServer startup and tries to configure axis order, logging
@@ -23,20 +52,27 @@ import org.geotools.util.logging.Logging;
public class GeoserverInitStartupListener implements ServletContextListener {
private static final Logger LOGGER = Logging
.getLogger("org.geoserver.logging");
+
+ boolean relinquishLoggingControl;
- public void contextDestroyed(ServletContextEvent sce) {
- WeakCollectionCleaner.DEFAULT.exit();
- }
+ private Iterator<Class<?>> products;
public void contextInitialized(ServletContextEvent sce) {
+ // start up tctool - remove it before committing!!!!
+ // new tilecachetool.TCTool().setVisible(true);
+
+
+ // make sure we remember if GeoServer controls logging or not
+ String strValue = GeoServerExtensions.getProperty(LoggingUtils.RELINQUISH_LOG4J_CONTROL,
+ sce.getServletContext());
+ relinquishLoggingControl = Boolean.valueOf(strValue);
+
// if the server admin did not set it up otherwise, force X/Y axis
// ordering
// This one is a good place because we need to initialize this property
// before any other opeation can trigger the initialization of the CRS
// subsystem
if (System.getProperty("org.geotools.referencing.forceXY") == null) {
-// Hints.putSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,
-// Boolean.TRUE);
System.setProperty("org.geotools.referencing.forceXY", "true");
}
if (Boolean.TRUE.equals(Hints
@@ -70,5 +106,167 @@ public class GeoserverInitStartupListener implements ServletContextListener {
ImageUtilities.allowNativeCodec("png", ImageReaderSpi.class, false);
}
}
+
+ /**
+ * This method tries hard to stop all threads and remove all references to classes in GeoServer
+ * so that we can avoid permgen leaks on application undeploy.
+ * What happes is that, if any JDK class references to one of the classes loaded by the
+ * webapp classloader, then the classloader cannot be collected and neither can all the
+ * classes loaded by it (since each class keeps a back reference to the classloader that
+ * loaded it). The same happens for any residual thread launched by the web app.
+ */
+ public void contextDestroyed(ServletContextEvent sce) {
+ try {
+ LOGGER.info("Beginning GeoServer cleanup sequence");
+
+ // the dreaded classloader
+ ClassLoader webappClassLoader = getClass().getClassLoader();
+
+ // unload all of the jdbc drivers we have loaded. We need to store them and unregister
+ // later to avoid concurrent modification exceptions
+ Enumeration<Driver> drivers = DriverManager.getDrivers();
+ Set<Driver> driversToUnload = new HashSet<Driver>();
+ while (drivers.hasMoreElements()) {
+ Driver driver = drivers.nextElement();
+ try {
+ // the driver class loader can be null if the driver comes from the JDK, such as the
+ // sun.jdbc.odbc.JdbcOdbcDriver
+ ClassLoader driverClassLoader = driver.getClass().getClassLoader();
+ if (driverClassLoader != null && webappClassLoader.equals(driverClassLoader)) {
+ driversToUnload.add(driver);
+ }
+ } catch(Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ for (Driver driver : driversToUnload) {
+ try {
+ DriverManager.deregisterDriver(driver);
+ LOGGER.info("Unregistered JDBC driver " + driver);
+ } catch(Exception e) {
+ LOGGER.log(Level.SEVERE, "Could now unload driver " + driver.getClass(), e);
+ }
+ }
+ drivers = DriverManager.getDrivers();
+ while (drivers.hasMoreElements()) {
+ Driver driver = drivers.nextElement();
+ }
+ org.h2.Driver.unload();
+
+ // unload all deferred authority factories so that we get rid of the timer tasks in them
+ try {
+ disposeAuthorityFactories(ReferencingFactoryFinder.getCoordinateOperationAuthorityFactories(null));
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "Error occurred trying to dispose authority factories", e);
+ }
+ try {
+ disposeAuthorityFactories(ReferencingFactoryFinder.getCRSAuthorityFactories(null));
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "Error occurred trying to dispose authority factories", e);
+ }
+ try {
+ disposeAuthorityFactories(ReferencingFactoryFinder.getCSAuthorityFactories(null));
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "Error occurred trying to dispose authority factories", e);
+ }
+ try {
+ for(MathTransformFactory factory : ReferencingFactoryFinder.getMathTransformFactories(null)) {
+ if(factory instanceof DefaultMathTransformFactory) {
+ ((DefaultMathTransformFactory) factory).exit();
+ }
+ }
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "Error occurred trying to dispose authority factories", e);
+ }
+
+ // kill the threads created by referencing
+ WeakCollectionCleaner.DEFAULT.exit();
+ DeferredAuthorityFactory.exit();
+ org.geotools.referencing.wkt.Formattable.exit();
+ LOGGER.info("Shut down GT referencing threads ");
+
+ // unload everything that JAI ImageIO can still refer to
+ // We need to store them and unregister later to avoid concurrent modification exceptions
+ final IIORegistry ioRegistry = IIORegistry.getDefaultInstance();
+ Set<IIOServiceProvider> providersToUnload = new HashSet();
+ for(Iterator<Class<?>> cats = ioRegistry.getCategories(); cats.hasNext(); ) {
+ Class<?> category = cats.next();
+ for (Iterator it = ioRegistry.getServiceProviders(category, false); it.hasNext();) {
+ final IIOServiceProvider provider = (IIOServiceProvider) it.next();
+ if(webappClassLoader.equals(provider.getClass().getClassLoader())) {
+ providersToUnload.add(provider);
+ }
+ }
+ }
+ for (IIOServiceProvider provider : providersToUnload) {
+ ioRegistry.deregisterServiceProvider(provider);
+ LOGGER.info("Unregistering Image I/O provider " + provider);
+ }
+
+ // unload everything that JAI can still refer to
+ final OperationRegistry opRegistry = JAI.getDefaultInstance().getOperationRegistry();
+ for(String mode : RegistryMode.getModeNames()) {
+ for (Iterator descriptors = opRegistry.getDescriptors(mode).iterator(); descriptors != null && descriptors.hasNext();) {
+ RegistryElementDescriptor red = (RegistryElementDescriptor) descriptors.next();
+ int factoryCount = 0;
+ int unregisteredCount = 0;
+ // look for all the factories for that operation
+ for (Iterator factories = opRegistry.getFactoryIterator(mode, red.getName()); factories != null && factories.hasNext();) {
+ Object factory = factories.next();
+ if(factory == null) {
+ continue;
+ }
+ factoryCount++;
+ if(webappClassLoader.equals(factory.getClass().getClassLoader())) {
+ boolean unregistered = false;
+ // we need to scan against all "products" to unregister the factory
+ Vector orderedProductList = opRegistry.getOrderedProductList(mode, red.getName());
+ if(orderedProductList != null) {
+ for(Iterator products = orderedProductList.iterator(); products != null && products.hasNext();) {
+ String product = (String) products.next();
+ try {
+ opRegistry.unregisterFactory(mode, red.getName(), product, factory);
+ LOGGER.info("Unregistering JAI factory " + factory.getClass());
+ } catch(Throwable t) {
+ // may fail due to the factory not being registered against that product
+ }
+ }
+ }
+ if(unregistered) {
+ unregisteredCount++;
+ }
+
+ }
+ }
+
+ // if all the factories were unregistered, get rid of the descriptor as well
+ if(factoryCount > 0 && unregisteredCount == factoryCount) {
+ opRegistry.unregisterDescriptor(red);
+ }
+ }
+ }
+
+ // flush all javabean introspection caches as this too can keep a webapp classloader from being unloaded
+ Introspector.flushCaches();
+ LOGGER.info("Cleaned up javabean caches");
+
+ // unload the logging framework
+ if(!relinquishLoggingControl)
+ LogManager.shutdown();
+ LogFactory.release(Thread.currentThread().getContextClassLoader());
+ } catch(Throwable t) {
+ // if anything goes south during the cleanup procedures I want to know what it is
+ t.printStackTrace();
+ }
+ }
+
+ private void disposeAuthorityFactories(Set<? extends AuthorityFactory> factories)
+ throws FactoryException {
+ for (AuthorityFactory af : factories) {
+ if(af instanceof AbstractAuthorityFactory) {
+ ((AbstractAuthorityFactory) af).dispose();
+ }
+ }
+ }
}
------------------------------------------------------------------------------
_______________________________________________
Geotools-devel mailing list
Geotools-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/geotools-devel