Author: markt Date: Wed Feb 17 21:16:40 2016 New Revision: 1730946 URL: http://svn.apache.org/viewvc?rev=1730946&view=rev Log: Refactor class loading so JAR scanning does not trigger the caching of the byte[] for every scanned class until the class is loaded.
Modified: tomcat/trunk/java/org/apache/catalina/loader/ResourceEntry.java tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java tomcat/trunk/webapps/docs/changelog.xml 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=1730946&r1=1730945&r2=1730946&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/loader/ResourceEntry.java (original) +++ tomcat/trunk/java/org/apache/catalina/loader/ResourceEntry.java Wed Feb 17 21:16:40 2016 @@ -14,13 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - package org.apache.catalina.loader; import java.net.URL; -import java.security.cert.Certificate; -import java.util.jar.Manifest; + +import org.apache.catalina.WebResource; /** * Resource entry. @@ -55,23 +53,6 @@ public class ResourceEntry { public URL source = null; - /** - * URL of the codebase from where the object was loaded. - */ - public URL codeBase = null; - - - /** - * Manifest (if the resource was loaded from a JAR). - */ - public Manifest manifest = null; - - - /** - * Certificates (if the resource was loaded from a JAR). - */ - public Certificate[] certificates = 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=1730946&r1=1730945&r2=1730946&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java (original) +++ tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java Wed Feb 17 21:16:40 2016 @@ -41,6 +41,7 @@ import java.security.PermissionCollectio import java.security.Policy; import java.security.PrivilegedAction; import java.security.ProtectionDomain; +import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -140,8 +141,6 @@ public abstract class WebappClassLoaderB private static final String CLASS_FILE_SUFFIX = ".class"; private static final String SERVICES_PREFIX = "/META-INF/services/"; - private static final Manifest MANIFEST_UNKNOWN = new Manifest(); - static { ClassLoader.registerAsParallelCapable(); JVM_THREAD_GROUP_NAMES.add(JVM_THREAD_GROUP_SYSTEM); @@ -153,19 +152,16 @@ public abstract class WebappClassLoaderB protected final String name; protected final String path; - protected final boolean manifestRequired; - PrivilegedFindResourceByName(String name, String path, boolean manifestRequired) { + PrivilegedFindResourceByName(String name, String path) { this.name = name; this.path = path; - this.manifestRequired = manifestRequired; } @Override public ResourceEntry run() { - return findResourceInternal(name, path, manifestRequired); + return findResourceInternal(name, path); } - } @@ -911,14 +907,15 @@ public abstract class WebappClassLoaderB if (entry == null) { if (securityManager != null) { PrivilegedAction<ResourceEntry> dp = - new PrivilegedFindResourceByName(name, path, false); + new PrivilegedFindResourceByName(name, path); entry = AccessController.doPrivileged(dp); } else { - entry = findResourceInternal(name, path, false); + entry = findResourceInternal(name, path); } } if (entry != null) { url = entry.source; + entry.webResource = null; } if ((url == null) && hasExternalRepositories) { @@ -931,7 +928,7 @@ public abstract class WebappClassLoaderB else log.debug(" --> Resource not found, returning null"); } - return (url); + return url; } @@ -2399,10 +2396,10 @@ public abstract class WebappClassLoaderB if (securityManager != null) { PrivilegedAction<ResourceEntry> dp = - new PrivilegedFindResourceByName(name, path, true); + new PrivilegedFindResourceByName(name, path); entry = AccessController.doPrivileged(dp); } else { - entry = findResourceInternal(name, path, true); + entry = findResourceInternal(name, path); } if (entry == null) { @@ -2418,10 +2415,43 @@ public abstract class WebappClassLoaderB if (clazz != null) return clazz; - if (entry.binaryContent == null) { + WebResource webResource = entry.webResource; + if (webResource == null) { + webResource = resources.getResource(path); + } else { + entry.webResource = null; + } + + if (webResource == null) { return null; } + Manifest manifest = webResource.getManifest(); + URL codeBase = webResource.getCodeBase(); + Certificate[] certificates = webResource.getCertificates(); + byte[] binaryContent = webResource.getContent(); + + if (transformers.size() > 0) { + // If the resource is a class just being loaded, decorate it + // with any attached transformers + String className = name.endsWith(CLASS_FILE_SUFFIX) ? + name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name; + String internalName = className.replace(".", "/"); + + for (ClassFileTransformer transformer : this.transformers) { + try { + byte[] transformed = transformer.transform( + this, internalName, null, null, binaryContent); + if (transformed != null) { + binaryContent = transformed; + } + } catch (IllegalClassFormatException e) { + log.error(sm.getString("webappClassLoader.transformError", name), e); + return null; + } + } + } + // Looking up the package String packageName = null; int pos = name.lastIndexOf('.'); @@ -2435,12 +2465,10 @@ public abstract class WebappClassLoaderB // Define the package (if null) if (pkg == null) { try { - if (entry.manifest == null) { - definePackage(packageName, null, null, null, null, - null, null, null); + if (manifest == null) { + definePackage(packageName, null, null, null, null, null, null, null); } else { - definePackage(packageName, entry.manifest, - entry.codeBase); + definePackage(packageName, manifest, codeBase); } } catch (IllegalArgumentException e) { // Ignore: normal error due to dual definition of package @@ -2455,10 +2483,9 @@ public abstract class WebappClassLoaderB if (pkg != null) { boolean sealCheck = true; if (pkg.isSealed()) { - sealCheck = pkg.isSealed(entry.codeBase); + sealCheck = pkg.isSealed(codeBase); } else { - sealCheck = (entry.manifest == null) - || !isPackageSealed(packageName, entry.manifest); + sealCheck = (manifest == null) || !isPackageSealed(packageName, manifest); } if (!sealCheck) throw new SecurityException @@ -2469,9 +2496,8 @@ public abstract class WebappClassLoaderB } try { - clazz = defineClass(name, entry.binaryContent, 0, - entry.binaryContent.length, - new CodeSource(entry.codeBase, entry.certificates)); + clazz = defineClass(name, binaryContent, 0, + binaryContent.length, new CodeSource(codeBase, certificates)); } catch (UnsupportedClassVersionError ucve) { throw new UnsupportedClassVersionError( ucve.getLocalizedMessage() + " " + @@ -2481,10 +2507,6 @@ public abstract class WebappClassLoaderB // Now the class has been defined, clear the elements of the local // resource cache that are no longer required. entry.loadedClass = clazz; - entry.binaryContent = null; - entry.codeBase = null; - entry.manifest = null; - entry.certificates = null; // Retain entry.source in case of a getResourceAsStream() call on // the class file after the class has been defined. } @@ -2522,11 +2544,9 @@ public abstract class WebappClassLoaderB * * @param name the resource name * @param path the resource path - * @param manifestRequired will load an associated manifest * @return the loaded resource, or null if the resource isn't found */ - protected ResourceEntry findResourceInternal(final String name, final String path, - boolean manifestRequired) { + protected ResourceEntry findResourceInternal(final String name, final String path) { checkStateForResourceLoading(name); @@ -2538,25 +2558,9 @@ public abstract class WebappClassLoaderB ResourceEntry entry = resourceEntries.get(path); if (entry != null) { - if (manifestRequired && entry.manifest == MANIFEST_UNKNOWN) { - // This resource was added to the cache when a request was made - // for the resource that did not need the manifest. Now the - // manifest is required, the cache entry needs to be updated. - resource = resources.getClassLoaderResource(path); - entry.manifest = resource.getManifest(); - } return entry; } - boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX); - boolean isCacheable = isClassResource; - if (!isCacheable) { - isCacheable = path.startsWith(SERVICES_PREFIX); - } - - - boolean fileNeedConvert = false; - resource = resources.getClassLoaderResource(path); if (!resource.exists()) { @@ -2565,29 +2569,27 @@ public abstract class WebappClassLoaderB entry = new ResourceEntry(); entry.source = resource.getURL(); - entry.codeBase = resource.getCodeBase(); 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) It is a class file since the binary content is only cached - * until the class has been loaded - * or - * b) The file needs conversion to address encoding issues (see + * a) The file needs conversion to address encoding issues (see * below) * or - * c) The resource is a service provider configuration file located + * 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 (isCacheable || fileNeedConvert) { + if (path.startsWith(SERVICES_PREFIX) || fileNeedConvert) { byte[] binaryContent = resource.getContent(); if (binaryContent != null) { if (fileNeedConvert) { @@ -2603,38 +2605,6 @@ public abstract class WebappClassLoaderB } } entry.binaryContent = binaryContent; - // The certificates and manifest are made available as a side - // effect of reading the binary content - entry.certificates = resource.getCertificates(); - } - } - - if (manifestRequired) { - entry.manifest = resource.getManifest(); - } else { - entry.manifest = MANIFEST_UNKNOWN; - } - - if (isClassResource && entry.binaryContent != null && - this.transformers.size() > 0) { - // If the resource is a class just being loaded, decorate it - // with any attached transformers - String className = name.endsWith(CLASS_FILE_SUFFIX) ? - name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name; - String internalName = className.replace(".", "/"); - - for (ClassFileTransformer transformer : this.transformers) { - try { - byte[] transformed = transformer.transform( - this, internalName, null, null, entry.binaryContent - ); - if (transformed != null) { - entry.binaryContent = transformed; - } - } catch (IllegalClassFormatException e) { - log.error(sm.getString("webappClassLoader.transformError", name), e); - return null; - } } } Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1730946&r1=1730945&r2=1730946&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Wed Feb 17 21:16:40 2016 @@ -80,6 +80,10 @@ Make checking for var and map replacement in RewriteValve a bit stricter and correct detection of colon in var replacement. (fschumacher) </add> + <fix> + Refactor the web application class loader to reduce the impact of JAR + scanning on the memory footprint of the web application. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org