开始
我们学习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,那这个世界乱套了