Tomcat之 --类加载机制

开始


我们学习JAVA的时候,都知道JAVA是跑在JVM虚拟机上的,而类加载器则是将编译后的class做加载,校验,转换解析,初始化,最终形成被虚拟机可以直接使用的JAVA类型,这便是我们知道的类加载机制。这是类加载器的执行的必须流程


ClassLoader类


classLoader类是java.lang包中一个类,他的作用是根据类名,将类名所指定的类加载,生成特定的字节码,然后从这些字节码中再实例化出一个java.lang.class的实例。我们来具体看下classLoader的流程。


protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  //省略部分代码。。。
 }        

首先是loadClass,loadClass做的事情加载name类,返回Class类实例,在这个方法其中,有这么个方法调用,findLoadClass(),这个方法是检查这个name的类 是否被加载过,这是一个Native方法,说明具体的检查是由虚拟机完成的


如果已经被虚拟机加载过,那么直接返回这个实例过的类,若没有,那么就需要去实例化他了

具体的过程,通过getParent() 我们首先拿到父类的加载器,然后通过父类的加载器去完成类的实例,这便是我们所说的双亲委派模型,我在看到这段代码的时候也是不理解 为什么我们要这么做,在查阅了《深入理解JAVA虚拟机》这本书中这么解释。


使用双亲委派模型来组织类加载器之间的关系,这样做的一个显而易见的好处就是JAVA类随着他的类加载器一起具备了一种带有优先级的层次关系。比如说,java.lang.Object类,无论哪个类加载器加载都需要加载这个类,最终都是委派给最顶端的启动类加载器来加载,因此Object类在程序的各种加载器在环境中都是同一个类。如果没有双亲委派模型,各个类去自行加载的话,用户自己编写一个java.lang.Object类,并放到classPath中,这样系统便会出现多个不同的Object类,那就会一团糟


当然我又有疑问了,如果父类无法完成类加载的时候,这时候怎么办,在classLoader中针对这种情况,很简单,我们再调用本身的findClass来实行类加载,也就是文章开头说的那些过程,这些过程都是在JVM中执行的。


当然我们也是可以自己实现类加载的,比如某些特定需求,从文件读取的.class文件去让jvm加载,其实也很简单,我们只需要重写classLoader类中的findClass方法就可以了。


Tomcat的类加载器


由上我们大概了解到了java类加载的整个过程,我们再来看下Tomcat中是怎么实现自己的类加载的。Tomcat是一个servlet容器,servlet对java.net包进行了封装,让用户忽略了底层socket的一系列过程。


我们知道,一个Tomcat可以运行多个JAVA进程实例,那么就会出现这么一个疑问,我们上面说,一个java进程只有一个启动类加载器,而Tomcat也是JAVA写的,那我们是怎么维护多个进程实例在Tomcat上面跑的时候的类加载状态呢。


我们来show代码

在 org.apache.catelina核心包的工厂方法 ClassLoaderFactory文件里的以下方法

public static ClassLoader createClassLoader(List<Repository> repositories,
                                            final ClassLoader parent)
    throws Exception {

    if (log.isDebugEnabled())
        log.debug("Creating new class loader");

    // Construct the "class path" for this class loader
    Set<URL> set = new LinkedHashSet<>();

    //省略中间一堆对配置文件的加载,以及一堆验证校验....

    return AccessController.doPrivileged(
            new PrivilegedAction<URLClassLoader>() {
                @Override
                public URLClassLoader run() {
                    if (parent == null)
                        return new URLClassLoader(array);
                    else
                        return new URLClassLoader(array, parent);
                }
            });
}


由上代码的最后URlClassLoader run 方法 ,我们来看下,这个提供了让我们可以对文件目录,jar包,网络上获取class文件,并进行加载,他的父类是ClassLoader!



所以,在我们Tomcat启动的时候,当处于lib,目录下的class,jar文件都已经被加载了,而Tomcat的lib下面又是什么呢,使我们所有公共可见的class,以及各自web服务的jar包,这样便打破了双亲委派模型


这样我们解决了主要的几个问题。


1.多个应用相互之间不冲突

2.避免了重复加载,减少了内存的占用。

3.而对于内部的servlet的api,这些都是由Tomcat容器自己加载过了


当然更加细节的是,这儿运用了线程的上下文切换加载器,主要通过对Thread类setContextClassLoader()方法进行设置


ps::下回分解



题外话:


其实无论双亲委派亦或者破坏双亲委派,本质都是保护Java.lang.Object的安全,唯一性,毕竟在我们JAVA的世界里,如果出现多个Object,那这个世界乱套了





暂无评论