6

类加载机制大家应该已经非常熟悉了,采取双亲委派机制,当加载一个类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成加载任务,就成功返回;如果父类无法加载,才由自己加载。

双亲委派机制的作用:防止内存中出现多份相同的字节码。

其他规则
1.隐式加载:在当前类中所有new的对象,如果没有被加载,则使用当前类的类加载器加载 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B
2.不同类加载器加载的类是不同的,通过类加载器+类全路径来唯一标识一个类

JVM预定义的三种类加载器
1.Bootstrap ClassLoader:启动类加载器,它负责将JAVA_HOME/lib下面的类库加载到内存中,如rt.jar;启动类加载器是由C++写的二进制代码,不是java类,在JVM启动的时候Bootstrap就已经启动。
2.Extension ClassLoader:标准扩展类加载器,它负责加载JAVA_HOME/lib/ext或由系统变量java.ext.dir指定位置中的类库加载到内存中。
3.APP ClassLoader:系统类加载器(System ClassLoader),它负责将类路径CLASSPATH中的类库加载到内存。

加载顺序图如下:

图片描述

图中的BootStrapClassLoader、ExtClassLoader、APPClassLoader不是真正的继承关系,只是逻辑上的上下级类加载器;

实际上的类关系如下图:

图片描述

可以看到ExtClassLoader和APPCLassLoader都继承自URLClassLoader,也就证实了二者并非真正的继承关系;

通过上图可以看到最顶层的类是抽象类ClassLoader:是所有类加载器的基类(除了启动类加载器),定义了类加载最核心的操作;
SecureClassLoader:添加了关联类源码、关联系统权限支持
URLClassLoader:支持从jar文件和文件夹中获取class
ExtClassLoader:扩展类加载器Extension ClassLoader
APPClassLoader:系统类加载器,也称为System ClassLoader

ClassLoader:

父子类加载器是通过ClassLoader一个parent属性来标识,APPClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器是null。

ClassLoader提供了两个构造器,一个是没有参数的,一个是有参数的;如下图:

图片描述

图片描述

没有参数的构造器默认将系统类加载器作为parent加载器;
有参数的构造器将参数指定的加载器作为父类加载器;

Launcher:

ExtClassLoader和AppClassLoader都是Launcher的子类,在ClassLoader初始化或者直接通过ClassLoader的getSystemClassLoader()获取的时候会调用initSystemClassLoader(),从而调用sun.misc.Launcher.getLauncher(),将系统类加载器赋值给ClassLoader的scl变量;

我们看下Launcher类初始化的时候都做了什么工作,如图:

图片描述

主要是三部工作:
1.创建ExtClassLoader
2.创建AppClassLoader
3.将线程系统类加载器设置为线程上下文类加载器,什么是上线文类加载器?

线程上下文类加载器
java提供了为很多服务商提供了接口,简称SPI(Service Provider Interface),具体的实现由各厂商提供,例如mysql驱动,oracle驱动等。例如:mysql驱动加载接口类在rt.jar中,由启动类加载器加载,具体实现类在mysql驱动包中,驱动包一般放到我们自己的程序路径lib下,应该由系统类加载器加载;但是在使用如下代码进行数据库连接使用操作的时候,就会出现在rt.jar中要加载驱动包里代码的情况(类加载器是启动类加载器),由隐式加载规则可知,驱动包也要使用启动类加载器加载,由类加载机制可知,是无法通过启动类加载器来加载的;那这种情况怎么办呢,就要通过线程上下文类加载器来解决。

上面描述的情况如下:使用jdbc进行数据库操作如下

1.Class.forName("com.mysql.jdbc.Driver");// 加载mysql驱动
2.Connection conn = DriverManager.getConnection(url);//创建连接
3.Statement stmt = conn.createStatement();//得到statement对象
4.操作数据库  关闭连接。。

第一步在实例化Driver时,会调用DriverManager的registerDriver()方法收集divers,将驱动类注册到DriverManager容器中,DriverManage的drivers容器:

图片描述

注册的代码:

图片描述

Class.forName("com.mysql.jdbc.Driver")相当于:
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class driversClass = loader.loadClass("com.mysql.jdbc.Driver");
driversClass.newInstance();
由此可见com.mysql.jdbc.Driver是由我们自己应用类加载器AppClassLoader进行加载;

第二步通过DriverManager.getConnection(url),会循环获取drivers中的driver,调用具体driver实现里的cnnect()方法,进行连接

图片描述

caller.getClassLoader()是启动类加载器为null,因此callerCL为系统类加载器

图片描述

getConnection通过isDriverAllowed方法校验类是否有权限被加载

图片描述

通过AppClassLoader来加载Driver看是否和已注册的Driver是同一个类,如果是则调用driver的connect方法

在java6以后,引入了service provider概念,在/META-INF/services/java.sql.Driver文件中配置需要加载的驱动类,
在DriverManager初始化的时候会调用loadInitialDrivers方法,

图片描述

会使用AppClassLoader进行加载,所以在自己程序中可以不用Class.forName显示调用。

上面包类结构如下图:

图片描述

tomcat类加载
我们运行tomcat的多个实例,不想安装tomcat软件副本,我们可以配置多个工作目录,每个运行实例独占一个工作目录,但是共享一个安装目录。
变量解释:
CATALINA_HOME:tomcat安装目录,多个工作目录可共享安装目录
CATALINA_BASE:tomcat工作目录,tomcat每个运行实例需要使用自己的conf、logs、temp、webapps、work、shared目录,CATALINA_BASE就是指向这个目录

如下图:两个应用公用CATALINA_HOME,CATALINA_BASE指向各自工作目录

图片描述
图片描述

首先看下tomcat在启动时类初始化类加载器过程

图片描述

首先是创建commonClassLoader,commonClassLoader加载的是配置文件catalina.properties中配置的

图片描述

common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
例如在我们的服务器上路径是:
/opt/soft/tomcat/lib、/opt/soft/tomcat/lib/*.jar

因为server.loader和shared.loader未配置具体加载目录信息,catalinaLoader和sharedLoader默认为commonLoader(在tomcat5以后catalinaLoader和sharedLoader默认不启用)

图片描述

tomcat一共定义了两种类加载器
StandardClassLoader:实例化commonloader、catalinaLoader、sharedLoader,不提供热部署功能,遵循双亲委派机制
WebappClassLoader:和context级容器相关联,加载web程序,支持其加载路径下资源改变后重新加载,不遵循双亲委派机制。

其类继承关系如下:

图片描述

除此之外还有两个类:WebappLoader和VirtualWebappLoader,该两个类不是类加载器,只是对WebappClassLoader做了封装,对热部署、生命周期控制等功能做了一些控制;
VirtualWebappLoader是WebappLoader的子类,主要是和conf/context.xml这个文件相关联,主要功能是加载context.xml配置文件中设置的一些java类库,由于WebappClassLoader只能加载WEB-INF/class和WEB-INF/lib下的类库。而想扩展一下加载路径又不想添加到WEB-INF/lib中的时候,可以配置在context.xml文件中。

自己实现类记载器只要实现findclass即可,这里为了实现特殊目的而override了loadClass();WebappClassLoader重写了loadClass方法,先自己加载,如果加载不了再进行其他操作。

所以在Tomcat 6中默认情况下,不是完全按照先Tomcat的lib再Web应用的lib这种顺序去加载类。
Jar包的加载顺序是:
1)JRE中的Java基础包
2)Web应用WEB-INF/lib下的包
3)Tomcat/lib下的包

如果想要在Web应用间共享一些Jar包,则不仅需要将公共包放在Tomcat的lib下,还要删掉Web应用lib中的包,否则Tomcat启动时还是会优先加载Web应用lib下的包的。

如果想要自己指定一个Tomcatlib和Web应用lib之外的ClassPath,除了修改Tomcat启动脚本外,可以为不同Web应用的Context指定一个VirtualWebappLoader,但源码注释中写到不推荐在生产环境中使用。


帅帅的波
193 声望12 粉丝