You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2016/02/19 18:09:36 UTC

svn commit: r1731275 - in /tomcat/trunk/java/org/apache/catalina: loader/ResourceEntry.java loader/WebappClassLoaderBase.java webresources/Cache.java webresources/FileResource.java

Author: markt
Date: Fri Feb 19 17:09:35 2016
New Revision: 1731275

URL: http://svn.apache.org/viewvc?rev=1731275&view=rev
Log:
Refactoring to reduce the impact on the memory footprint of the resource cache within the web application class loader.
Main changes:
- WebResources caches everything apart from classes
- WebResources is responsible for EBCDIC conversion for properties files
- The class loader cache now only caches the last modified time of any resource loaded through the class loader and loaded classes.

Modified:
    tomcat/trunk/java/org/apache/catalina/loader/ResourceEntry.java
    tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java
    tomcat/trunk/java/org/apache/catalina/webresources/Cache.java
    tomcat/trunk/java/org/apache/catalina/webresources/FileResource.java

Modified: tomcat/trunk/java/org/apache/catalina/loader/ResourceEntry.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/ResourceEntry.java?rev=1731275&r1=1731274&r2=1731275&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/loader/ResourceEntry.java (original)
+++ tomcat/trunk/java/org/apache/catalina/loader/ResourceEntry.java Fri Feb 19 17:09:35 2016
@@ -16,10 +16,6 @@
  */
 package org.apache.catalina.loader;
 
-import java.net.URL;
-
-import org.apache.catalina.WebResource;
-
 /**
  * Resource entry.
  *
@@ -27,32 +23,16 @@ import org.apache.catalina.WebResource;
  */
 public class ResourceEntry {
 
-
     /**
-     * The "last modified" time of the origin file at the time this class
+     * The "last modified" time of the origin file at the time this resource
      * was loaded, in milliseconds since the epoch.
      */
     public long lastModified = -1;
 
 
     /**
-     * Binary content of the resource.
-     */
-    public byte[] binaryContent = null;
-
-
-    /**
      * Loaded class.
      */
     public volatile Class<?> loadedClass = null;
-
-
-    /**
-     * URL source from where the object was loaded.
-     */
-    public URL source = null;
-
-
-    public WebResource webResource = null;
 }
 

Modified: tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java?rev=1731275&r1=1731274&r2=1731275&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java (original)
+++ tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java Fri Feb 19 17:09:35 2016
@@ -16,7 +16,6 @@
  */
 package org.apache.catalina.loader;
 
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FilePermission;
 import java.io.IOException;
@@ -32,7 +31,6 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.nio.charset.StandardCharsets;
 import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.CodeSource;
@@ -139,7 +137,6 @@ public abstract class WebappClassLoaderB
     private static final String JVM_THREAD_GROUP_SYSTEM = "system";
 
     private static final String CLASS_FILE_SUFFIX = ".class";
-    private static final String SERVICES_PREFIX = "/META-INF/services/";
 
     static {
         ClassLoader.registerAsParallelCapable();
@@ -147,20 +144,18 @@ public abstract class WebappClassLoaderB
         JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
     }
 
-    protected class PrivilegedFindResourceByName
-        implements PrivilegedAction<ResourceEntry> {
+    protected class PrivilegedFindClassByName
+        implements PrivilegedAction<Class<?>> {
 
         protected final String name;
-        protected final String path;
 
-        PrivilegedFindResourceByName(String name, String path) {
+        PrivilegedFindClassByName(String name) {
             this.name = name;
-            this.path = path;
         }
 
         @Override
-        public ResourceEntry run() {
-            return findResourceInternal(name, path);
+        public Class<?> run() {
+            return findClassInternal(name);
         }
     }
 
@@ -327,12 +322,6 @@ public abstract class WebappClassLoaderB
 
 
     /**
-     * need conversion for properties files
-     */
-    protected boolean needConvert = false;
-
-
-    /**
      * All permission.
      */
     protected final Permission allPermission = new java.security.AllPermission();
@@ -688,7 +677,6 @@ public abstract class WebappClassLoaderB
         base.resources = this.resources;
         base.delegate = this.delegate;
         base.state = LifecycleState.NEW;
-        base.needConvert = this.needConvert;
         base.clearReferencesStatic = this.clearReferencesStatic;
         base.clearReferencesStopThreads = this.clearReferencesStopThreads;
         base.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
@@ -831,7 +819,13 @@ public abstract class WebappClassLoaderB
             if (log.isTraceEnabled())
                 log.trace("      findClassInternal(" + name + ")");
             try {
-                clazz = findClassInternal(name);
+                if (securityManager != null) {
+                    PrivilegedAction<Class<?>> dp =
+                        new PrivilegedFindClassByName(name);
+                    clazz = AccessController.doPrivileged(dp);
+                } else {
+                    clazz = findClassInternal(name);
+                }
             } catch(AccessControlException ace) {
                 log.warn("WebappClassLoader.findClassInternal(" + name
                         + ") security exception: " + ace.getMessage(), ace);
@@ -903,19 +897,10 @@ public abstract class WebappClassLoaderB
 
         String path = nameToPath(name);
 
-        ResourceEntry entry = resourceEntries.get(path);
-        if (entry == null) {
-            if (securityManager != null) {
-                PrivilegedAction<ResourceEntry> dp =
-                    new PrivilegedFindResourceByName(name, path);
-                entry = AccessController.doPrivileged(dp);
-            } else {
-                entry = findResourceInternal(name, path);
-            }
-        }
-        if (entry != null) {
-            url = entry.source;
-            entry.webResource = null;
+        WebResource resource = resources.getClassLoaderResource(path);
+        if (resource.exists()) {
+            url = resource.getURL();
+            trackLastModified(path, resource);
         }
 
         if ((url == null) && hasExternalRepositories) {
@@ -929,7 +914,18 @@ public abstract class WebappClassLoaderB
                 log.debug("    --> Resource not found, returning null");
         }
         return url;
+    }
+
 
+    private void trackLastModified(String path, WebResource resource) {
+        if (resourceEntries.containsKey(path)) {
+            return;
+        }
+        ResourceEntry entry = new ResourceEntry();
+        entry.lastModified = resource.getLastModified();
+        synchronized(resourceEntries) {
+            resourceEntries.putIfAbsent(path, entry);
+        }
     }
 
 
@@ -1064,14 +1060,6 @@ public abstract class WebappClassLoaderB
 
         InputStream stream = null;
 
-        // (0) Check for a cached copy of this resource
-        stream = findLoadedResource(name);
-        if (stream != null) {
-            if (log.isDebugEnabled())
-                log.debug("  --> Returning stream from cache");
-            return (stream);
-        }
-
         boolean delegateFirst = delegate || filter(name, false);
 
         // (1) Delegate to parent if requested
@@ -1080,30 +1068,33 @@ public abstract class WebappClassLoaderB
                 log.debug("  Delegating to parent classloader " + parent);
             stream = parent.getResourceAsStream(name);
             if (stream != null) {
-                // FIXME - cache???
                 if (log.isDebugEnabled())
                     log.debug("  --> Returning stream from parent");
-                return (stream);
+                return stream;
             }
         }
 
         // (2) Search local repositories
         if (log.isDebugEnabled())
             log.debug("  Searching local repositories");
-        URL url = findResource(name);
-        if (url != null) {
-            // FIXME - cache???
+        String path = nameToPath(name);
+        WebResource resource = resources.getClassLoaderResource(path);
+        if (resource.exists()) {
+            stream = resource.getInputStream();
+            trackLastModified(path, resource);
+        }
+        try {
+            if (hasExternalRepositories && stream == null) {
+                URL url = super.findResource(name);
+                stream = url.openStream();
+            }
+        } catch (IOException e) {
+            // Ignore
+        }
+        if (stream != null) {
             if (log.isDebugEnabled())
                 log.debug("  --> Returning stream from local");
-            stream = findLoadedResource(name);
-            try {
-                if (hasExternalRepositories && (stream == null))
-                    stream = url.openStream();
-            } catch (IOException e) {
-                // Ignore
-            }
-            if (stream != null)
-                return (stream);
+            return stream;
         }
 
         // (3) Delegate to parent unconditionally
@@ -1112,18 +1103,16 @@ public abstract class WebappClassLoaderB
                 log.debug("  Delegating to parent classloader unconditionally " + parent);
             stream = parent.getResourceAsStream(name);
             if (stream != null) {
-                // FIXME - cache???
                 if (log.isDebugEnabled())
                     log.debug("  --> Returning stream from parent");
-                return (stream);
+                return stream;
             }
         }
 
         // (4) Resource was not found
         if (log.isDebugEnabled())
             log.debug("  --> Resource not found, returning null");
-        return (null);
-
+        return null;
     }
 
 
@@ -1465,17 +1454,6 @@ public abstract class WebappClassLoaderB
             }
         }
 
-        state = LifecycleState.STARTING;
-
-        try {
-            String encoding = System.getProperty("file.encoding");
-            if (encoding.indexOf("EBCDIC") != -1) {
-                needConvert = true;
-            }
-        } catch (SecurityException e) {
-            // Ignore
-        }
-
         state = LifecycleState.STARTED;
     }
 
@@ -2389,20 +2367,39 @@ public abstract class WebappClassLoaderB
      */
     protected Class<?> findClassInternal(String name) {
 
-        String path = binaryNameToPath(name, true);
+        checkStateForResourceLoading(name);
 
-        ResourceEntry entry = null;
+        String path = binaryNameToPath(name, true);
 
-        if (securityManager != null) {
-            PrivilegedAction<ResourceEntry> dp =
-                new PrivilegedFindResourceByName(name, path);
-            entry = AccessController.doPrivileged(dp);
-        } else {
-            entry = findResourceInternal(name, path);
+        if (name == null || path == null) {
+            return null;
         }
 
+        ResourceEntry entry = resourceEntries.get(path);
+        WebResource resource = null;
+
         if (entry == null) {
-            return null;
+            resource = resources.getClassLoaderResource(path);
+
+            if (!resource.exists()) {
+                return null;
+            }
+
+            entry = new ResourceEntry();
+            entry.lastModified = resource.getLastModified();
+
+            // Add the entry in the local resource repository
+            synchronized (resourceEntries) {
+                // Ensures that all the threads which may be in a race to load
+                // a particular class all end up with the same ResourceEntry
+                // instance
+                ResourceEntry entry2 = resourceEntries.get(path);
+                if (entry2 == null) {
+                    resourceEntries.put(path, entry);
+                } else {
+                    entry = entry2;
+                }
+            }
         }
 
         Class<?> clazz = entry.loadedClass;
@@ -2414,21 +2411,18 @@ public abstract class WebappClassLoaderB
             if (clazz != null)
                 return clazz;
 
-            WebResource webResource = entry.webResource;
-            if (webResource == null) {
-                webResource = resources.getClassLoaderResource(path);
-            } else {
-                entry.webResource = null;
+            if (resource == null) {
+                resource = resources.getClassLoaderResource(path);
             }
 
-            if (!webResource.exists()) {
+            if (!resource.exists()) {
                 return null;
             }
 
-            byte[] binaryContent = webResource.getContent();
-            Manifest manifest = webResource.getManifest();
-            URL codeBase = webResource.getCodeBase();
-            Certificate[] certificates = webResource.getCertificates();
+            byte[] binaryContent = resource.getContent();
+            Manifest manifest = resource.getManifest();
+            URL codeBase = resource.getCodeBase();
+            Certificate[] certificates = resource.getCertificates();
 
             if (transformers.size() > 0) {
                 // If the resource is a class just being loaded, decorate it
@@ -2503,11 +2497,7 @@ public abstract class WebappClassLoaderB
                         sm.getString("webappClassLoader.wrongVersion",
                                 name));
             }
-            // Now the class has been defined, clear the elements of the local
-            // resource cache that are no longer required.
             entry.loadedClass = clazz;
-            // Retain entry.source in case of a getResourceAsStream() call on
-            // the class file after the class has been defined.
         }
 
         return clazz;
@@ -2539,90 +2529,6 @@ public abstract class WebappClassLoaderB
 
 
     /**
-     * Find specified resource in local repositories.
-     *
-     * @param name the resource name
-     * @param path the resource path
-     * @return the loaded resource, or null if the resource isn't found
-     */
-    protected ResourceEntry findResourceInternal(final String name, final String path) {
-
-        checkStateForResourceLoading(name);
-
-        if (name == null || path == null) {
-            return null;
-        }
-
-        ResourceEntry entry = resourceEntries.get(path);
-        if (entry != null) {
-            return entry;
-        }
-
-        WebResource resource = resources.getClassLoaderResource(path);
-
-        if (!resource.exists()) {
-            return null;
-        }
-
-        entry = new ResourceEntry();
-        entry.source = resource.getURL();
-        entry.lastModified = resource.getLastModified();
-        entry.webResource = resource;
-
-        boolean fileNeedConvert = false;
-        if (needConvert && path.endsWith(".properties")) {
-            fileNeedConvert = true;
-        }
-
-        /* Only cache the binary content if there is some content
-         * available and one of the following is true:
-         * a) The file needs conversion to address encoding issues (see
-         *    below)
-         *    or
-         * b) The resource is a service provider configuration file located
-         *    under META-INF/services
-         *
-         * In all other cases do not cache the content to prevent
-         * excessive memory usage if large resources are present (see
-         * https://bz.apache.org/bugzilla/show_bug.cgi?id=53081).
-         */
-        if (path.startsWith(SERVICES_PREFIX) || fileNeedConvert) {
-            byte[] binaryContent = resource.getContent();
-            if (binaryContent != null) {
-                 if (fileNeedConvert) {
-                    // Workaround for certain files on platforms that use
-                    // EBCDIC encoding, when they are read through FileInputStream.
-                    // See commit message of rev.303915 for details
-                    // http://svn.apache.org/viewvc?view=revision&revision=303915
-                    String str = new String(binaryContent);
-                    try {
-                        binaryContent = str.getBytes(StandardCharsets.UTF_8);
-                    } catch (Exception e) {
-                        return null;
-                    }
-                }
-                entry.binaryContent = binaryContent;
-            }
-        }
-
-        // Add the entry in the local resource repository
-        synchronized (resourceEntries) {
-            // Ensures that all the threads which may be in a race to load
-            // a particular class all end up with the same ResourceEntry
-            // instance
-            ResourceEntry entry2 = resourceEntries.get(path);
-            if (entry2 == null) {
-                resourceEntries.put(path, entry);
-            } else {
-                entry = entry2;
-            }
-        }
-
-        return entry;
-    }
-
-
-    /**
      * Returns true if the specified package name is sealed according to the
      * given manifest.
      *
@@ -2648,35 +2554,6 @@ public abstract class WebappClassLoaderB
     }
 
 
-    /**
-     * Finds the resource with the given name if it has previously been
-     * loaded and cached by this class loader, and return an input stream
-     * to the resource data.  If this resource has not been cached, return
-     * <code>null</code>.
-     *
-     * @param name Name of the resource to return
-     * @return a stream to the loaded resource
-     */
-    protected InputStream findLoadedResource(String name) {
-
-        String path = nameToPath(name);
-
-        ResourceEntry entry = resourceEntries.get(path);
-        if (entry != null) {
-            if (entry.binaryContent != null)
-                return new ByteArrayInputStream(entry.binaryContent);
-            else {
-                try {
-                    return entry.source.openStream();
-                } catch (IOException ioe) {
-                    // Ignore
-                }
-            }
-        }
-        return null;
-    }
-
-
     /**
      * Finds the class with the given name if it has previously been
      * loaded and cached by this class loader, and return the Class object.

Modified: tomcat/trunk/java/org/apache/catalina/webresources/Cache.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/webresources/Cache.java?rev=1731275&r1=1731274&r2=1731275&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/webresources/Cache.java (original)
+++ tomcat/trunk/java/org/apache/catalina/webresources/Cache.java Fri Feb 19 17:09:35 2016
@@ -203,9 +203,10 @@ public class Cache {
     }
 
     private boolean noCache(String path) {
-        // Don't cache resources used by the class loader (it has its own cache)
-        if (path.startsWith("/WEB-INF/classes/") ||
-                path.startsWith("/WEB-INF/lib/")) {
+        // Don't cache classes. The class loader handles this.
+        if (path.endsWith(".class") &&
+                (path.startsWith("/WEB-INF/classes/") ||
+                        path.startsWith("/WEB-INF/lib/"))) {
             return true;
         }
         return false;

Modified: tomcat/trunk/java/org/apache/catalina/webresources/FileResource.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/webresources/FileResource.java?rev=1731275&r1=1731274&r2=1731275&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/webresources/FileResource.java (original)
+++ tomcat/trunk/java/org/apache/catalina/webresources/FileResource.java Fri Feb 19 17:09:35 2016
@@ -16,6 +16,7 @@
  */
 package org.apache.catalina.webresources;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -23,6 +24,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.security.cert.Certificate;
@@ -40,10 +42,26 @@ public class FileResource extends Abstra
 
     private static final Log log = LogFactory.getLog(FileResource.class);
 
+    private static final boolean PROPERTIES_NEED_CONVERT;
+    static {
+        boolean isEBCDIC = false;
+        try {
+            String encoding = System.getProperty("file.encoding");
+            if (encoding.indexOf("EBCDIC") != -1) {
+                isEBCDIC = true;
+            }
+        } catch (SecurityException e) {
+            // Ignore
+        }
+        PROPERTIES_NEED_CONVERT = isEBCDIC;
+    }
+
+
     private final File resource;
     private final String name;
     private final boolean readOnly;
     private final Manifest manifest;
+    private final boolean needConvert;
 
     public FileResource(WebResourceRoot root, String webAppPath,
             File resource, boolean readOnly, Manifest manifest) {
@@ -69,6 +87,7 @@ public class FileResource extends Abstra
 
         this.readOnly = readOnly;
         this.manifest = manifest;
+        this.needConvert = PROPERTIES_NEED_CONVERT && name.endsWith(".properties");
     }
 
     @Override
@@ -111,6 +130,14 @@ public class FileResource extends Abstra
 
     @Override
     public long getContentLength() {
+        if (needConvert) {
+            byte[] content = getContent();
+            if (content == null) {
+                return -1;
+            } else {
+                return content.length;
+            }
+        }
         return resource.length();
     }
 
@@ -134,6 +161,14 @@ public class FileResource extends Abstra
 
     @Override
     protected InputStream doGetInputStream() {
+        if (needConvert) {
+            byte[] content = getContent();
+            if (content == null) {
+                return null;
+            } else {
+                return new ByteArrayInputStream(content);
+            }
+        }
         try {
             return new FileInputStream(resource);
         } catch (FileNotFoundException fnfe) {
@@ -172,6 +207,18 @@ public class FileResource extends Abstra
             }
         }
 
+        if (needConvert) {
+            // Workaround for certain files on platforms that use
+            // EBCDIC encoding, when they are read through FileInputStream.
+            // See commit message of rev.303915 for original details
+            // http://svn.apache.org/viewvc?view=revision&revision=303915
+            String str = new String(result);
+            try {
+                result = str.getBytes(StandardCharsets.UTF_8);
+            } catch (Exception e) {
+                result = null;
+            }
+        }
         return result;
     }
 



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