Parental delegation model

Class loader partitioning

  • Bootstrap ClassLoader
  • Extension ClassLoader
  • Application Classloader

The startup class loader is responsible for loading into the virtual machine memory libraries that are stored in the

\lib directory or in the path specified by the -xbootCLASspath parameter and are recognized by the virtual machine. The startup class loader cannot be referenced directly by a Java program. The extended classloader is implemented by sun.misc.Launcher$ExtClassLoader, which loads all libraries in the

\lib\ext directory, or in the path specified by the java.ext.dirs system variable. Developers can use the extended classloader directly. The application ClassLoader is implemented by sun.misc.Launcher$app-classloader. The application class loader is also called the system class loader. It is responsible for loading the libraries specified on the user’s ClassPath. If there is no custom class loader in the application, the application class loader is generally the default class loader in the application.

Parents delegate the working process of the model





protectedClass<? > loadClass(String name,boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loadedClass<? > c = findLoadedClass(name);if (c == null) {
            long t0 = System.nanoTime();
            try {
                if(parent ! =null) {
                    c = parent.loadClass(name, false);
                } else{ c = findBootstrapClassOrNull(name); }}catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); }}if (resolve) {
            resolveClass(c);
        }
        returnc; }}Copy the code

The role of parental delegation models

Java classes have a hierarchy of priorities along with their classloaders. For example, the java.lang.Object class, which is stored in rt.jar, is ultimately delegated to the boot class loader at the top of the model by any classloader. Therefore, the Object class is the same class in the program’s various classloader environments.

Break the parent delegate model

Java has SPI interfaces provided by many core libraries that allow third parties to provide implementations for these interfaces, common examples being JDBC, JNDI, and so on. The problem is that the SPI interface is part of the core library and is loaded by the startup class loader, whereas the implementation classes of the SPI interface are loaded by the application class loader. According to the parental delegation model, Bootstrap ClassLoader cannot delegate Application ClassLoader to load classes (see the Java SPI example in Section 2.1 of Dubbo SPI for Java SPI). To solve this problem, we introduce a Thread Context ClassLoader. This classloader can be set using the setContextClassLoader() method of the java.lang.Thread class. If it has not been set when the Thread is created, it will inherit one from the parent Thread, if it has not been set at the global level of the application. This class loader is the application class loader by default. DriverManager initializes the JDBC driver class

/**
 * Load the initial JDBC drivers by checking the System property
 * jdbc.properties and then use the {@code ServiceLoader} mechanism
 */
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}
Copy the code
private static void loadInitialDrivers(a) {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run(a) {
                return System.getProperty("jdbc.drivers"); }}); }catch (Exception ex) {
        drivers = null;
    }
    // If the driver is packaged as a Service Provider, load it.
    // Get all the drivers through the classloader
    // exposed as a java.sql.Driver.class service.
    // ServiceLoader.load() replaces the sun.misc.Providers()

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run(a) {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated. * It may be the case that the driver class may not be there * i.e. there may be a packaged driver with the service class * as implementation of java.sql.Driver but the actual class * may be missing. In that case a java.util.ServiceConfigurationError * will be thrown at runtime by the VM trying to locate * and load the service. * * Adding a try catch block to catch those runtime errors * if driver not available in classpath but it's * packaged as service and that service is there in classpath. */
            try{
                while(driversIterator.hasNext()) { driversIterator.next(); }}catch(Throwable t) {
            // Do nothing
            }
            return null; }}); println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: "+ ex); }}}Copy the code

Note the load method of the ServiceLoader above, where the thread context classloader is used.

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
Copy the code

Tomcat’s class loading architecture

Tomcat as a Web container needs to address the following issues:

  1. Deployed on the same server two Web applications using Java class library can realize mutual isolation, because two different applications may depend on the different versions of the same third party libraries cannot ask a library only a in a server, the server shall guarantee the class library of two applications can be used independently.
  2. Java class libraries used by two Web applications deployed on the same service can be shared with each other.
  3. The server needs to secure itself from deployed Web applications as much as possible. In general, the libraries used by the server should be separate from the application libraries.
  4. Most Web servers that support JSP applications need to support Hotswap.

The Tomcat class loading architecture is shown below:

  1. Classes in the CommonClassLoader load path can be used by Tomcat and all Web applications
  2. Classes in the CatalinaClassLoader loading path can be used by Tomcat and are not visible to all Web applications
  3. Classes in the SharedClassLoader load path are common to all Web applications, but not visible to Tomcat itself
  4. Classes in the WebappClassLoader loading path are visible only to the current WebApp, not to Tomcat and other Web applications

As can be seen from the delegation relationship:

  1. The WebappClassLoader instances are isolated from each other
  2. CommonClassLoader and SharedClassLoader implement common class libraries
  3. Classes that CatalinaClassLoader and SharedClassLoader can load are isolated from each other
  4. The scope of JasperLoader is only the Class from which the JSP file is compiled

Start with the Bootstrap. main method of Tomcat

/**
 * Main method and entry point when starting Tomcat via the provided
 * scripts.
 *
 * @param args Command line arguments to be processed
 */
public static void main(String args[]) {

    synchronized (daemonLock) {
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            // When running as a service the call to stop will be on a new
            // thread so make sure the correct class loader is used to
            // prevent a range of class not found exceptions.Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); }}try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }

        if (command.equals("startd")) {
            args[args.length - 1] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[args.length - 1] = "stop";
            daemon.stop();
        } else if (command.equals("start")) {
            daemon.setAwait(true);
            daemon.load(args);
            daemon.start();
            if (null == daemon.getServer()) {
                System.exit(1); }}else if (command.equals("stop")) {
            daemon.stopServer(args);
        } else if (command.equals("configtest")) {
            daemon.load(args);
            if (null == daemon.getServer()) {
                System.exit(1);
            }
            System.exit(0);
        } else {
            log.warn("Bootstrap: command \"" + command + "\" does not exist."); }}catch (Throwable t) {
        // Unwrap the Exception for clearer error reporting
        if (t instanceofInvocationTargetException && t.getCause() ! =null) {
            t = t.getCause();
        }
        handleThrowable(t);
        t.printStackTrace();
        System.exit(1); }}Copy the code

The init method, thread.currentthread ().setcontextclassloader, sets the currentThread context class loader to catalinaLoader.

/**
 * Initialize daemon.
 * @throws Exception Fatal initialization error
 */
public void init(a) throws Exception {

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
        log.debug("Loading startup class"); Class<? > startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // Set the shared extensions class loader
    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");
    String methodName = "setParentClassLoader"; Class<? > paramTypes[] =new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;

}
Copy the code

Look at the initClassLoaders method.

private void initClassLoaders(a) {
    try {
        commonLoader = createClassLoader("common".null);
        if( commonLoader == null ) {
            // no config file, default to this loader - we might be in a 'single' env.
            commonLoader=this.getClass().getClassLoader();
        }
        catalinaLoader = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1); }}Copy the code

You can see that commonLoader, catalinaLoader, and sharedLoader are all initialized in initClassLoaders. For the Tomcat6.x version, Only specified the tomcat/conf/catalina. The properties of server configuration file. The loader and Shared loader will truly establish CatalinaClassLoader after and SharedClassLoader instance, Otherwise, instances of these two classloaders will be used instead of CommonClassLoader, and the default configuration file does not set these two loaders. So tomcat6.x combines /common, /server, and /shared into a /lib directory by default. This directory contains the same class libraries as the /common directory.

common.loader="${catalina.base}/lib"."${catalina.base}/lib/*.jar"."${catalina.home}/lib"."${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
Copy the code
private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception {

    String value = CatalinaProperties.getProperty(name + ".loader");
    if ((value == null) || (value.equals("")))
        return parent;

    value = replace(value);

    List<Repository> repositories = new ArrayList<>();

    String[] repositoryPaths = getPaths(value);

    for (String repository : repositoryPaths) {
        // Check for a JAR URL repository
        try {
            @SuppressWarnings("unused")
            URL url = new URL(repository);
            repositories.add(new Repository(repository, RepositoryType.URL));
            continue;
        } catch (MalformedURLException e) {
            // Ignore
        }

        // Local repository
        if (repository.endsWith("*.jar")) {
            repository = repository.substring
                (0, repository.length() - "*.jar".length());
            repositories.add(new Repository(repository, RepositoryType.GLOB));
        } else if (repository.endsWith(".jar")) {
            repositories.add(new Repository(repository, RepositoryType.JAR));
        } else {
            repositories.add(newRepository(repository, RepositoryType.DIR)); }}return ClassLoaderFactory.createClassLoader(repositories, parent);
}
Copy the code