Explore Tomcat and learn ClassLoader

Imprint

tomcat 8.5.xx
jdk 1.8.xxx

The introduction of the ClassLoader structure

write picture description here

  ClassLoader cl = this.getClass().getClassLoader();
  while(cl != null){
       System.out.println(cl.getClass().getCanonicalName());
       cl = cl.getParent();
  }

will print the following
org.apache.catalina.loader.ParallelWebappClassLoader
java.net.URLClassLoader
sun.misc.Launcher.AppClassLoader
sun.misc.Launcher.ExtClassLoader

Why is the following content printed?

AppClassLoader (loading system configuration item java.class.path) and ExtClassLoader (loading system configuration item java.ext.dirs) are class loaders that come with jdk

ClassLoader load classpath
BootStrapClassLoader sun.boot.class.path(\jdk1.8.0_131\jre\lib*.jar)
ExtClassLoader java.ext.dirs(\jdk1.8.0_131\jre\lib\ext*.jar,C:\WINDOWS\Sun\Java\lib\ext*.jar)
AppClassLoader java.class.path

hint

AppClassLoader can be specified by the -classpath or -cp parameter when starting the java program

So why is BootStrapClassLoader (loading rt.jar) gone?

This is also a point

I read a blog post today http://blog.csdn.net/briblue/article/details/54973413#t32

The above says that BootstrapClassLoader is implemented in C++

Bootstrap ClassLoader is written by C/C++, it is part of the virtual machine itself, so it is not a JAVA class, that is, it cannot be referenced in java code, rt.jar is loaded through Bootstrap class loader when JVM starts For the class files in the core jar package, the previous int.class and String.class are loaded by it. Then, as we have analyzed earlier, the JVM initializes sun.misc.Launcher and creates Extension ClassLoader and AppClassLoader instances. And set ExtClassLoader as the parent loader of AppClassLoader. Bootstrap has no parent loader, but it can act as the parent loader of a ClassLoader. Such as ExtClassLoader. This can also explain the previous phenomenon of getting Null through the getParent method of ExtClassLoader. The specific reason will soon be known.

delegation mechanism

Let's first take a look at how the delegation mechanism of the class loader is implemented in the java code. Let's take a look at ClassLoaderthe loadClass()method first.

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> 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 stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

(1) In the findLoadedClassmethod, it is actually findLoadedClass0a packaging method. This method is a native method. The main function is to check whether this class has been loaded in the current virtual machine.
(2) Next, the class will be loaded from the parent chain first, and the class will be loaded from the top level first. If the loading is successful, it will return. Continue to load if unsuccessful. Add to load the XXXX class and the XXXX class has not been loaded. The current ClassLoader is AppClassLoader, so it will be loaded from BootStrapClassLoader first. If not, it will be loaded from ExtClasLoader. If it is still not loaded, it will be loaded from AppClassLoader, otherwise ClassNotFundException will be thrown.

The above is the realization of the delegation mechanism.

custom class loader

Then let's think about another question. We all know that our war package directory structure is as follows
write picture description here

If we load a class manually, what is the difference between loading from classes and loading from lib?

load from classes

/**
     * 查找class 可以在这个方法之中修改逻辑,从而优先加载自己的类,
     * 当然java.lang.String包里面并不可以,因为它在jvm启动的时候已经加载过了
     *
     * @param name 类名
     *
     * @return class or null
     *
     * @throws ClassNotFoundException 类未寻找到异常
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> aClass = findLoadedClass(name);
        if (aClass != null) {
            return aClass;
        }

        if (name.startsWith(packageName)) {
            byte[] classData = getData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                return defineClass(name, classData, 0, classData.length);
            }
        }else{
            return super.loadClass(name);
        }
    }

    private byte[] getData(String name) {
        String path = classPath + File.separator + name.replace(".", File.separator)+ ".class";
        try {
            ByteArrayOutputStream result = new ByteArrayOutputStream();
            URL url = new URL(path);
            InputStream is = url.openStream();
            byte[] buffer = new byte[2048];
            int num = 0;
            while((num = is.read(buffer)) != -1){
                result.write(buffer, 0 , num);
            }
            return result.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

test program

        String classPath = "file:D:\\classloader\\classes";
        String className = "com.wuhulala.future.FutureExample";
        NetClassLoader cl = new NetClassLoader(classPath);
        cl.loadClass(className);

Once you get the class, you can do various operations based on reflection.

Load classes from jar packages

No encapsulation, URLClassLoader is used directly

String jarFilePath = "file:F:\\code\\javase\\java_up\\jar\\commons-beanutils-1.8.3.jar";
        URL[] urls = new URL[1];
        urls[0] = new URL(jarFilePath);
        URLClassLoader classLoader = URLClassLoader.newInstance(urls);
        Class<?> fastHashMapClazz = classLoader.loadClass("org.apache.commons.collections.FastHashMap");
        Object fastHashMap = fastHashMapClazz.newInstance();
        System.out.println(fastHashMap);

There really is no difference. . .

URLClassLoader encapsulates many effective methods relative to ClassLoader, and inherits SecureClassLoader, which has a certain degree of security, but I have not used the features here, and there are similar treatments in Spring! ! !

So what's so clever about tomcat over our own implementation?

tomcat class loader implementation

First look at the class loader of tomcat, which inherits WebappClassLoaderBase. This Base class is the coverage that truly implements the function of class loading.
public class WebappClassLoader extends WebappClassLoaderBase

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
       // 先上个锁,防止并发出现问题
        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;

            // Log access to stopped class loader
            // 判断当前类是否正在加载,和Spring中的bean创建一样
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
            // 判断tomcat是否已经加载过,根据resourceEntries这个缓存容器,实现采用的是ConcurrentHashMap
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                // 是否需要link
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.1) Check our previously loaded class cache
            // 判断当前jvm中是否已经加载过此类呢?

            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.2) Try loading the class with the system class loader, to prevent
            //       the webapp from overriding Java SE classes. This implements
            //       SRV.10.7.2
            //  尝试开始加载此类
            String resourceName = binaryNameToPath(name, false);
            // 先从BootstrapClassLoader中尝试加载
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                // Use getResource as it won't trigger an expensive
                // ClassNotFoundException if the resource is not available from
                // the Java SE class loader. However (see
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
                // details) when running under a security manager in rare cases
                // this call may trigger a ClassCircularityError.
                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
                // details of how this may trigger a StackOverflowError
                // Given these reported errors, catch Throwable to ensure any
                // other edge cases are also caught
                tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
            } catch (Throwable t) {
                // Swallow all exceptions apart from those that must be re-thrown
                ExceptionUtils.handleThrowable(t);
                // The getResource() trick won't work for this class. We have to
                // try loading it directly and accept that we might get a
                // ClassNotFoundException.
                tryLoadingFromJavaseLoader = true;
            }

            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (0.5) Permission to access this class when using a SecurityManager
            // 判断是否有权限操作此类
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        log.info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }

            // 判断类是否需要父类加载器,即tomcat内置的类加载器,tomcat8.5.x使用的是一个URLClassLoader去加载,待会分析以下filter方法
            boolean delegateLoad = delegate || filter(name, true);

            // (1) Delegate to our parent if requested
            // 通过父classLoader去加载
            if (delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader1 " + parent);
                try {
                    clazz = Class.forName(name, false, 
);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (2) Search local repositories
            // 查询依照/WEB-INF/classes /WEB-INF/lib这个顺序寻找类
            if (log.isDebugEnabled())
                log.debug("  Searching local repositories");
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from local repository");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            // (3) Delegate to parent unconditionally
            // 无条件的使用委托机制加载
            if (!delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader at end: " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }

        throw new ClassNotFoundException(name);
    }

Let's explain why it is loaded in the order of /WEB-INF/classes and /WEB-INF/lib?

 WebResource classes = resources.getResource("/WEB-INF/classes");
        if (classes.isDirectory() && classes.canRead()) {
            localRepositories.add(classes.getURL());
        }
        WebResource[] jars = resources.listResources("/WEB-INF/lib");
        for (WebResource jar : jars) {
            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
                localRepositories.add(jar.getURL());
                jarModificationTimes.put(
                        jar.getName(), Long.valueOf(jar.getLastModified()));
            }
        }

The above localRepositories is a List data structure

There is also the above filter method, which is to determine whether these classes are built-in classes of tomcat. We can look at the package directory of tomcat. To mention that these classes are loaded by StandardClassLoader, look at the following code
write picture description here

protected boolean filter(String name, boolean isClassName) {

        if (name == null)
            return false;

        char ch;
        if (name.startsWith("javax")) {
            /* 5 == length("javax") */
            if (name.length() == 5) {
                return false;
            }
            ch = name.charAt(5);
            if (isClassName && ch == '.') {
                /* 6 == length("javax.") */
                if (name.startsWith("servlet.jsp.jstl.", 6)) {
                    return false;
                }
                if (name.startsWith("el.", 6) ||
                    name.startsWith("servlet.", 6) ||
                    name.startsWith("websocket.", 6) ||
                    name.startsWith("security.auth.message.", 6)) {
                    return true;
                }
            } else if (!isClassName && ch == '/') {
                /* 6 == length("javax/") */
                if (name.startsWith("servlet/jsp/jstl/", 6)) {
                    return false;
                }
                if (name.startsWith("el/", 6) ||
                    name.startsWith("servlet/", 6) ||
                    name.startsWith("websocket/", 6) ||
                    name.startsWith("security/auth/message/", 6)) {
                    return true;
                }
            }
        } else if (name.startsWith("org")) {
            /* 3 == length("org") */
            if (name.length() == 3) {
                return false;
            }
            ch = name.charAt(3);
            if (isClassName && ch == '.') {
                /* 4 == length("org.") */
                if (name.startsWith("apache.", 4)) {
                    /* 11 == length("org.apache.") */
                    if (name.startsWith("tomcat.jdbc.", 11)) {
                        return false;
                    }
                    if (name.startsWith("el.", 11) ||
                        name.startsWith("catalina.", 11) ||
                        name.startsWith("jasper.", 11) ||
                        name.startsWith("juli.", 11) ||
                        name.startsWith("tomcat.", 11) ||
                        name.startsWith("naming.", 11) ||
                        name.startsWith("coyote.", 11)) {
                        return true;
                    }
                }
            } else if (!isClassName && ch == '/') {
                /* 4 == length("org/") */
                if (name.startsWith("apache/", 4)) {
                    /* 11 == length("org/apache/") */
                    if (name.startsWith("tomcat/jdbc/", 11)) {
                        return false;
                    }
                    if (name.startsWith("el/", 11) ||
                        name.startsWith("catalina/", 11) ||
                        name.startsWith("jasper/", 11) ||
                        name.startsWith("juli/", 11) ||
                        name.startsWith("tomcat/", 11) ||
                        name.startsWith("naming/", 11) ||
                        name.startsWith("coyote/", 11)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

The above is the class loading content of the entire tomcat, welcome to shoot bricks! ! !

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326403832&siteId=291194637