Hi, I have found, that once loaded jsp-servlets are never unloaded.
To test I just configured tomcat to process *.html files by JspServlet and then traversed jdk documentation. The result was not very exciting - after browsing ~ 150 pages tomcat cried "java.lang.OutOfMemoryError: Java heap space" and started not to work... So maybe it would be not a bad idea to try to keep in memeory just some fixed number of jsp-servlets ? I have written a sample implementation of such a policy, but it is not very elegant as internal structure containing jsp-servlets, it seems, was not designed for such actions... Regards, Yarick.
diff -rdu apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/EmbeddedServletOptions.java apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/EmbeddedServletOptions.java --- apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/EmbeddedServletOptions.java 2006-01-03 10:14:04.000000000 +0100 +++ apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/EmbeddedServletOptions.java 2006-02-21 13:26:44.984221000 +0100 @@ -174,6 +174,17 @@ */ private boolean xpoweredBy; + /** + * The maxim number of loaded jsps per web-application. If there are more + * jsps loaded, they will be unloaded. + */ + private int maxLoadedJsps = 20; + + /** + * How often it is tryed to unload jsps (in seconds) + */ + private int jspUnloadTestInterval = 4; + public String getProperty(String name ) { return settings.getProperty( name ); } @@ -355,6 +366,14 @@ return null; } + public int getMaxLoadedJsps() { + return maxLoadedJsps; + } + + public int getJspUnloadTestInterval() { + return jspUnloadTestInterval; + } + /** * Create an EmbeddedServletOptions object using data available from * ServletConfig and ServletContext. @@ -636,6 +655,40 @@ } } + String maxLoadedJsps = config.getInitParameter("maxLoadedJsps"); + if (maxLoadedJsps != null) { + try { + this.maxLoadedJsps = Integer.parseInt(maxLoadedJsps); + if (this.maxLoadedJsps <= 0) { + this.maxLoadedJsps = 20; + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.maxLoadedJsps", ""+this.maxLoadedJsps)); + } + } + } catch(NumberFormatException ex) { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.maxLoadedJsps", ""+this.maxLoadedJsps)); + } + } + } + + String jspUnloadTestInterval = config.getInitParameter("jspUnloadTestInterval"); + if (jspUnloadTestInterval != null) { + try { + this.jspUnloadTestInterval = Integer.parseInt(jspUnloadTestInterval); + if (this.jspUnloadTestInterval <= 0) { + this.jspUnloadTestInterval = 4; + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.jspUnloadTestInterval",""+this.jspUnloadTestInterval)); + } + } + } catch(NumberFormatException ex) { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.jspUnloadTestInterval",""+this.jspUnloadTestInterval)); + } + } + } + // Setup the global Tag Libraries location cache for this // web-application. tldLocationsCache = new TldLocationsCache(context); diff -rdu apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/JspC.java apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/JspC.java --- apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/JspC.java 2006-01-03 10:14:04.000000000 +0100 +++ apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/JspC.java 2006-02-21 13:27:07.568387400 +0100 @@ -448,6 +448,14 @@ return cache; } + public int getMaxLoadedJsps() { + return 0; + } + + public int getJspUnloadTestInterval() { + return 0; + } + /** * Background compilation check intervals in seconds */ diff -rdu apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/Options.java apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/Options.java --- apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/Options.java 2006-01-03 10:14:04.000000000 +0100 +++ apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/Options.java 2006-02-21 13:27:24.210173000 +0100 @@ -184,5 +184,16 @@ * @return the Map(String uri, TreeNode tld) instance. */ public Map getCache(); + + /** + * The maxim number of loaded jsps per web-application. If there are more + * jsps loaded, they will be unloaded. + */ + public int getMaxLoadedJsps(); + + /** + * How often it is tryed to unload jsps (in seconds) + */ + public int getJspUnloadTestInterval(); } diff -rdu apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/compiler/JspRuntimeContext.java apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/compiler/JspRuntimeContext.java --- apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/compiler/JspRuntimeContext.java 2006-01-03 10:14:02.000000000 +0100 +++ apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/compiler/JspRuntimeContext.java 2006-02-21 13:16:29.004201800 +0100 @@ -25,10 +25,7 @@ import java.security.PermissionCollection; import java.security.Policy; import java.security.cert.Certificate; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; // yarick: java.util.HashMap -> java.util.* import javax.servlet.ServletContext; import javax.servlet.jsp.JspFactory; @@ -148,8 +145,8 @@ /** * Maps JSP pages to their JspServletWrapper's */ - private Map jsps = Collections.synchronizedMap( new HashMap()); - + private Map jsps = Collections.synchronizedMap( new LinkedHashMap( 16, 0.75f, true ) ); // yarick: HashMap -> LinkedHashMap + /** * The background thread. @@ -192,6 +189,21 @@ } /** + * Get an already existing JspServletWrapper and increases services count. + * + * @param jspUri JSP URI + * @return JspServletWrapper for JSP + */ + public JspServletWrapper getWrapperAndIncService(String jspUri) { + JspServletWrapper jswr; + synchronized( jsps ) { + jswr = (JspServletWrapper) jsps.get(jspUri); + if( null != jswr ) jswr.incService(); + } + return jswr; + } + + /** * Remove a JspServletWrapper. * * @param jspUri JSP URI of JspServletWrapper to remove @@ -521,4 +533,28 @@ } +// { inserted by Yarick + + /** must be called from synchronized by [EMAIL PROTECTED] org.apache.jasper.servlet.JspServlet} context */ + public JspServletWrapper getJspForUnload() + { + int MAX_UNLOADABLE_JSPS = 10; + if( jsps.size() > MAX_UNLOADABLE_JSPS ) { + JspServletWrapper jsw = null; + synchronized( jsps ) { + Iterator it = jsps.entrySet().iterator(); + for( int rest_jsps=jsps.size(); rest_jsps > MAX_UNLOADABLE_JSPS && it.hasNext(); --rest_jsps ) { + jsw = (JspServletWrapper) ( (Map.Entry) it.next() ).getValue(); + if( jsw.getExecutingServicesCount() == 0 && !jsw.isTagFile() ) { + it.remove(); + return jsw; + } + } + } + } + return null; + } + +// } inserted by Yarick + } diff -rdu apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/servlet/JspServlet.java apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/servlet/JspServlet.java --- apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/servlet/JspServlet.java 2006-01-03 10:14:02.000000000 +0100 +++ apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/servlet/JspServlet.java 2006-02-21 13:37:08.060784200 +0100 @@ -17,8 +17,9 @@ package org.apache.jasper.servlet; import java.io.IOException; +import java.io.File; import java.lang.reflect.Constructor; -import java.util.Enumeration; +import java.util.*; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; @@ -97,8 +98,10 @@ options = new EmbeddedServletOptions(config, context); } rctxt = new JspRuntimeContext(context, options); - - if (log.isDebugEnabled()) { + + startUnloadJspsThread(); // inserted by yarick + + if (log.isDebugEnabled()) { log.debug(Localizer.getMessage("jsp.message.scratch.dir.is", options.getScratchDir().toString())); log.debug(Localizer.getMessage("jsp.message.dont.modify.servlets")); @@ -278,7 +281,7 @@ if (log.isDebugEnabled()) { log.debug("JspServlet.destroy()"); } - + stopUnloadJspsThread(); // inserted by yarick rctxt.destroy(); } @@ -290,29 +293,99 @@ Throwable exception, boolean precompile) throws ServletException, IOException { - JspServletWrapper wrapper = - (JspServletWrapper) rctxt.getWrapper(jspUri); - if (wrapper == null) { - synchronized(this) { - wrapper = (JspServletWrapper) rctxt.getWrapper(jspUri); - if (wrapper == null) { - // Check if the requested JSP page exists, to avoid - // creating unnecessary directories and files. - if (null == context.getResource(jspUri)) { - response.sendError(HttpServletResponse.SC_NOT_FOUND, - jspUri); - return; + JspServletWrapper wrapper = null; + try { + wrapper = (JspServletWrapper) rctxt.getWrapperAndIncService(jspUri); + if( null == wrapper ) + synchronized(this) { + wrapper = (JspServletWrapper) rctxt.getWrapperAndIncService(jspUri); + if (wrapper == null) { + // Check if the requested JSP page exists, to avoid + // creating unnecessary directories and files. + if (null == context.getResource(jspUri)) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, + jspUri); + return; + } + boolean isErrorPage = exception != null; + wrapper = new JspServletWrapper(config, options, jspUri, + isErrorPage, rctxt); + rctxt.addWrapper(jspUri,wrapper); } - boolean isErrorPage = exception != null; - wrapper = new JspServletWrapper(config, options, jspUri, - isErrorPage, rctxt); - rctxt.addWrapper(jspUri,wrapper); + wrapper.incService(); + } + + /* dances around making it not possible to call servlet.init() before + * previous copy of servlet is unloaded ( called method servlet.destroy() ) + */ + String _destroyingUri = destroyingUri; + if( wrapper.getJspUri().equals( _destroyingUri ) ) + synchronized( _destroyingUri ) { + if( _destroyingUri == destroyingUri ) + _destroyingUri.wait(); } + + wrapper.service(request, response, precompile); + + } catch( InterruptedException ignore ) {} + finally { // inserted by yarick + synchronized(this) { + if( null != wrapper ) wrapper.decService(); } - } + } + } - wrapper.service(request, response, precompile); +// { inserted by yarick + private Thread unloadThread; + private String destroyingUri; + protected void startUnloadJspsThread() { + String pathToWebApp = context.getRealPath("/"); + if( pathToWebApp.endsWith( File.separator ) ) + pathToWebApp = pathToWebApp.substring( 0, pathToWebApp.length()-1 ); + + unloadThread = new Thread( "jspUnloader ["+pathToWebApp.substring( pathToWebApp.lastIndexOf( File.separatorChar ) )+( "/" )+"]" ) { + public void run() { runUnloadJspsThread(); } + }; + unloadThread.setDaemon( true ); + unloadThread.start(); + } + + protected void stopUnloadJspsThread() { + if( null != unloadThread ) { + unloadThread.interrupt(); + try { unloadThread.join( 10 * 1000 ); } // wait maximum 10 seconds + catch( InterruptedException ignore ) {} + unloadThread = null; + } } + protected void runUnloadJspsThread() { + try { + while( !Thread.currentThread().isInterrupted() ) { + JspServletWrapper jsw; + synchronized( this ) { + jsw = rctxt.getJspForUnload(); + if( null != jsw ) destroyingUri = new String( jsw.getJspUri() ); + } + + if( null == jsw ) + Thread.sleep( options.getJspUnloadTestInterval() * 1000 ); + else + /* dances around making it not possible to call servlet.init() before + * previous copy of servlet is unloaded ( called method servlet.destroy() ) + */ + synchronized( destroyingUri ) { + try { + jsw.destroy(); + } finally { + String prev_destroyingUri = destroyingUri; + destroyingUri = null; + prev_destroyingUri.notifyAll(); + } + } + } + } catch( InterruptedException exit ) {} + } +// } inserted by yarick } diff -rdu apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/servlet/JspServletWrapper.java apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/servlet/JspServletWrapper.java --- apache-tomcat-5.5.15-src-orig/jasper/jasper2/src/share/org/apache/jasper/servlet/JspServletWrapper.java 2006-01-03 10:14:04.000000000 +0100 +++ apache-tomcat-5.5.15-src-patched/jasper/jasper2/src/share/org/apache/jasper/servlet/JspServletWrapper.java 2006-02-21 13:24:43.999843400 +0100 @@ -86,6 +86,7 @@ private JasperException compileException; private long servletClassLastModifiedTime; private long lastModificationTest = 0L; + private int executingServicesCount; // yarick /* * JspServletWrapper for JSP pages. @@ -397,6 +398,10 @@ } } + synchronized public void incService() { ++executingServicesCount; } // yarick: inserted accounting of 'service' + synchronized public void decService() { --executingServicesCount; } // yarick: inserted accounting of 'service' + String getJspUri() { return jspUri; } // yarick: inserted + public void destroy() { if (theServlet != null) { theServlet.destroy(); @@ -416,6 +421,9 @@ this.lastModificationTest = lastModificationTest; } + /** @return Returns count of currently executing of method [EMAIL PROTECTED] #service} */ + public int getExecutingServicesCount() { return executingServicesCount; } // yarick + /** * <p>Attempts to construct a JasperException that contains helpful information * about what went wrong. Uses the JSP compiler system to translate the line
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]