Index: java/org/apache/catalina/loader/LockAwareURLClassLoader.java =================================================================== --- java/org/apache/catalina/loader/LockAwareURLClassLoader.java (revision 0) +++ java/org/apache/catalina/loader/LockAwareURLClassLoader.java (revision 0) @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.catalina.loader; + +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLStreamHandlerFactory; + +/** + * ClassLoader that is aware of JDK 7 improvements in synchronization policy in + * class loaders, and provides access to the relevant API through reflection. + */ +public class LockAwareURLClassLoader extends URLClassLoader { + + private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory + .getLog(LockAwareURLClassLoader.class); + + /** + * If JDK 7 API is available and its use is enabled, this field will contain + * a reference to the ClassLoader.registerAsParallelCapable + * method. To make use of this feature, each derived class has to call this + * method in its static initialization block. If the API is not available, + * or its use is not enabled, this field will be null. + */ + protected static final Method registerAsParallelCapableMethod; + + private static final Method getClassLoadingLockMethod; + + private static final boolean useClassNameAsLock; + + static { + final String propertyName = "org.apache.catalina.loader.WebappClassLoader.CLASSLOADER_LOCK_POLICY"; + final String defaultValue = "strict"; + String propertyValue = System.getProperty(propertyName); + if (!"strict".equals(propertyValue) && !"jdk7".equals(propertyValue) + && !"name".equals(propertyValue)) { + propertyValue = defaultValue; + } + if (log.isDebugEnabled()) { + log.debug(propertyName + ": " + propertyValue); + } + Method registerMethod = null; + Method getLockMethod = null; + boolean success = false; + if ("name".equals(propertyValue)) { + useClassNameAsLock = true; + } else { + useClassNameAsLock = false; + } + if ("jdk7".equals(propertyValue)) { + final Class classLoaderClass = ClassLoader.class; + try { + registerMethod = classLoaderClass + .getDeclaredMethod("registerAsParallelCapable"); + getLockMethod = classLoaderClass.getDeclaredMethod( + "getClassLoadingLock", String.class); + registerMethod.setAccessible(true); + if (Boolean.TRUE.equals(registerMethod.invoke(null))) { + success = true; + if (log.isDebugEnabled()) { + log.debug("Registered the class loader as parallelCapable"); + } + } + } catch (NoSuchMethodException ex) { + if (log.isDebugEnabled()) { + log.debug("Cannot register the class loader as parallelCapable"); + } + } catch (Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Failed to register the class loader as parallelCapable", + ex); + } + } + } + if (success) { + registerAsParallelCapableMethod = registerMethod; + getClassLoadingLockMethod = getLockMethod; + } else { + registerAsParallelCapableMethod = null; + getClassLoadingLockMethod = null; + } + } + + // ----------------------------------------------------------- Constructors + + /** + * Calls + * URLClassLoader(URL[], ClassLoader, URLStreamHandlerFactory). + * + * @param urls + * the URLs + * @param parent + * the parent ClassLoader in the delegation chain + * @param factory + * the URLStreamHandlerFactory + */ + public LockAwareURLClassLoader(URL[] urls, ClassLoader parent, + URLStreamHandlerFactory factory) { + super(urls, parent, factory); + } + + /** + * Calls URLClassLoader(URL[], ClassLoader). + * + * @param urls + * the URLs + * @param parent + * the parent ClassLoader in the delegation chain + */ + public LockAwareURLClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + /** + * Calls URLClassLoader(URL[]). + * + * @param urls + * the URLs + */ + public LockAwareURLClassLoader(URL[] urls) { + super(urls); + } + + // ------------------------------------------------------- + + /** + * Returns the lock object that has to be used when loading a particular + * class. By default returns a reference to this ClassLoader. This method + * may call JDK 7 method + * ClassLoader.getClassLoadingLock(String) if running with JDK + * 7 or later and Tomcat is configured to do so. + * + * @param className + * The name of the class to be loaded + * @return the lock + */ + protected Object getTomcatClassLoadingLock(String className) { + Object lock = this; + if (getClassLoadingLockMethod != null) { + try { + lock = getClassLoadingLockMethod.invoke(this, className); + } catch (Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Failed to call " + getClassLoadingLockMethod, + ex); + } + } + } else if (useClassNameAsLock) { + lock = className.intern(); + } + return lock; + } +} Index: java/org/apache/catalina/loader/ResourceEntry.java =================================================================== --- java/org/apache/catalina/loader/ResourceEntry.java (revision 922264) +++ java/org/apache/catalina/loader/ResourceEntry.java (working copy) @@ -47,7 +47,7 @@ /** * Loaded class. */ - public Class loadedClass = null; + public volatile Class loadedClass = null; /** Index: java/org/apache/catalina/loader/WebappClassLoader.java =================================================================== --- java/org/apache/catalina/loader/WebappClassLoader.java (revision 922264) +++ java/org/apache/catalina/loader/WebappClassLoader.java (working copy) @@ -32,7 +32,6 @@ import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; -import java.net.URLClassLoader; import java.security.AccessControlException; import java.security.AccessController; import java.security.CodeSource; @@ -112,7 +111,7 @@ * @version $Revision$ $Date$ */ public class WebappClassLoader - extends URLClassLoader + extends LockAwareURLClassLoader implements Reloader, Lifecycle { @@ -129,6 +128,23 @@ public static final boolean ENABLE_CLEAR_REFERENCES = Boolean.valueOf(System.getProperty("org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES", "true")).booleanValue(); + static { + if (LockAwareURLClassLoader.registerAsParallelCapableMethod != null) { + try { + Object success = LockAwareURLClassLoader.registerAsParallelCapableMethod + .invoke(null); + if (log.isDebugEnabled() && Boolean.TRUE.equals(success)) { + log.debug("Registered the class loader as parallelCapable"); + } + } catch (Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Failed to register the class loader as parallelCapable", + ex); + } + } + } + } + /** * @deprecated Not used */ @@ -1391,7 +1407,7 @@ public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - synchronized (name.intern()) { + synchronized (getTomcatClassLoadingLock(name)) { if (log.isDebugEnabled()) log.debug("loadClass(" + name + ", " + resolve + ")"); Class clazz = null; @@ -2469,7 +2485,7 @@ if (clazz != null) return clazz; - synchronized (name.intern()) { + synchronized (getTomcatClassLoadingLock(name)) { clazz = entry.loadedClass; if (clazz != null) return clazz; Index: java/org/apache/jasper/servlet/JasperLoader.java =================================================================== --- java/org/apache/jasper/servlet/JasperLoader.java (revision 922264) +++ java/org/apache/jasper/servlet/JasperLoader.java (working copy) @@ -20,11 +20,11 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.net.URLClassLoader; import java.security.CodeSource; import java.security.PermissionCollection; import org.apache.jasper.Constants; +import org.apache.catalina.loader.LockAwareURLClassLoader; /** * Class loader for loading servlet class files (corresponding to JSP files) @@ -34,14 +34,34 @@ * @author Harish Prabandham * @author Jean-Francois Arcand */ -public class JasperLoader extends URLClassLoader { +public class JasperLoader extends LockAwareURLClassLoader { + private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory + .getLog(JasperLoader.class); + private PermissionCollection permissionCollection; private CodeSource codeSource; private String className; private ClassLoader parent; private SecurityManager securityManager; + static { + if (LockAwareURLClassLoader.registerAsParallelCapableMethod != null) { + try { + Object success = LockAwareURLClassLoader.registerAsParallelCapableMethod + .invoke(null); + if (log.isDebugEnabled() && Boolean.TRUE.equals(success)) { + log.debug("Registered the class loader as parallelCapable"); + } + } catch (Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Failed to register the class loader as parallelCapable", + ex); + } + } + } + } + public JasperLoader(URL[] urls, ClassLoader parent, PermissionCollection permissionCollection, CodeSource codeSource) { @@ -131,7 +151,16 @@ return clazz; } - return findClass(name); + synchronized(getTomcatClassLoadingLock(name)) { + clazz = findLoadedClass(name); + if (clazz == null) { + clazz = findClass(name); + } + if( resolve ) { + resolveClass(clazz); + } + return clazz; + } } Index: webapps/docs/config/systemprops.xml =================================================================== --- webapps/docs/config/systemprops.xml (revision 922264) +++ webapps/docs/config/systemprops.xml (working copy) @@ -333,6 +333,25 @@

+ +

If equals to jdk7, Tomcat will try to use JDK 7 + ClassLoader class API to register WebappClassLoader and + JasperLoader as parallelCapable. If the API is not + available, Tomcat will fallback to the strict policy. +

+

If equals to strict, the default locking policy will be + used, which is to obtain a lock over the entire ClassLoader instance. +

+

If equals to name, the name of the class being loaded + will be used as the lock token. This option is experimental, and is + known to cause deadlocks (see bug 48903), but might work + with some JRE versions and settings.

+

If not specified, the default value of strict will be + used. +

+
+

The URL for the catalina.properties configuration file.