tomcat-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Yoav Shapira" <yo...@apache.org>
Subject Re: What's about unloading policy of jsp-servlets ?
Date Fri, 03 Mar 2006 12:57:05 GMT
What makes you think your OOME has to do with the number of JspServlet
instances?  Run a profiler, you might be surprised.

Yoav

On 3/3/06, Yaroslav Sokolov <yarick123@gmail.com> 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 {@link 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 {@link #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: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
>
>


--
Yoav Shapira
Senior Architect
Nimalex LLC
1 Mifflin Place, Suite 310
Cambridge, MA, USA
yoavs@computer.org / www.yoavshapira.com

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Mime
View raw message