Thanks for the patch...
This is a well known problem, JSPs are not unloaded unless the entire
webapp is unloaded. And to make things worse - by default all JSP
static content is compiled to strings, that take all the memory. I
think we have ( or had ) an option to generate some non .class file -
which could be more easily managed.
Unfortunately I'm not familiar enough with jasper code - but it looks
good to me. The model of keeping the entire jsp static content in
memory forever is IMO very broken... It may help cheat on some
benchmarks ( i.e. jsp versus html, etc ), but it's wrong for real
world.
Costin
On 3/3/06, Yaroslav Sokolov <[EMAIL PROTECTED]> wrote:
> 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]
>
>
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]