虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,转换,解析和初始化,最终造成能够被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。java
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用、卸载七个阶段。其中验证、准备和解析三个部分统称为链接。程序员
加载:加载是类加载的第一个阶段,这个阶段,首先要根据类的全限定名来获取定义此类的二进制字节六,讲字节六转化为方法区运行时数据结构。在 Java 堆生成一个表明这个类的 java.lang.class 对象,做为方法区的访问入口。web
验证:这一步的的目的是确保 class 文件的字节六包含的信息符合虚拟机的要求。sql
准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都会在方法区中进行分配。仅仅是类变量,不包括实例变量。数据库
public static int value = 123;
变量在准备阶段事后的初始值为0而不是123,123的赋值要在变量初始化之后才会完成。tomcat
解析:虚拟机将常量池内的符号引用替换为直接引用的过程。安全
初始化:初始化是类加载的最后一步,这一步会根据程序员给定的值去初始化一些资源。数据结构
加载是咱们使用一个类的第一步,加载是如何完成的那?app
虚拟机设计团队把类加载阶段中的经过一个类的全限定名来获取描述此类的二进制字节流这个动做放到 Java 虚拟机外部去实现,以便让程序本身去决定如何获取所须要的类,这个动做的代码模块称为类加载器
。webapp
对于一个类,都须要由加载它的类加载器和这个类自己一同确立其在 Java 虚拟机中的惟一性,比较两个类是否相等须要在这两个类是由同一个类加载器加载的前提下才有意义。
JVM 在加载类时默认采用的是双亲委派模型机制。通俗地讲,某个特定的类加载器在接到加载类的请求时,首先讲加载任务委托给父类加载器,所以全部加载请求最总都应该传送到顶层的启动类加载器中。若是父类没法完成加载请求,子类才会尝试本身加载。
因此,越基础的类会由越上层的加载器加载。
若是不使用双亲委派模型,用户本身写一个 Object 类放入 ClassPath,那么系统中将会出现多个不一样的 Object 类,Java 类型体系中最基础的行为也就无从保证。如今你能够尝试本身写一个名为 Object 的类,能够被编译,但永远没法运行。由于最后加载时都会先委派给父类去加载,在 rt.jar 搜寻自身目录时就会找到系统定义的 Object 类,因此你定义的 Object 类永远没法被加载和运行。
双亲委派模型的好处是保证了核心类库不配覆盖和篡改。
双亲委派模型并非一个强制性的约束模型,而是 Java 设计者推荐给开发者的类加载器实现方式。Java 世界中大部分的类加载器都遵循这个模型。
双亲委派模型第一次“被破坏”是由这个模型自身的缺陷致使的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),基础类之因此称为“基础”,是由于他们老是做为被用户代码调用的 API。那若是基础类又要调用回用户的代码,怎么办?
好比 JNDI 服务,JNDI 如今是 Java 的标准服务,他的代码由启动类加载器去加载(rt.jar),但 JNDI 须要由独立厂商实现并部署在应用程序的 Class Path 下的 JNDI 接口提供者的代码,启动类加载器不可能认识这些代码,由于启动类加载器的搜索范围找不到用户应用程序类。
为了解决这个问题,Java 团队设计了一个不太优雅的设计:线程上下文加载器这个类加载器能够经过java.lang.Thread类的setContextClassLoader() 方法进行设置,若是建立线程时还未设置,它将会从父线程中继承一个,若是在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器(Application ClassLoader)。
有了这个线程上下文加载器,JNDI 服务使用线程上下文加载器去加载所须要的 SPI 代码。也就是父类加载器请求子类加载器完成类加载的动做,这就打破了双亲委派模型。典型的例子有 JNDI 和 JDBC 等。
Tomcat 的类加载机制是违反了双亲委派原则的,对于一些为加载的非基础类(Object,String)等,各个 web 应用本身的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给 commonClassLoader 走双亲委派模型。总体结构以下:
这其中 JDK 提供的类加载器分别是:
Tomcat 自定义类加载器分别是:
在 JDBC 4.0 以后咱们须要使用 Class.forName 来加载驱动程序了,咱们只须要把驱动的 jar 包放到工程的类加载路径里,那么驱动就会被自动加载。
这个自动加载采用的技术叫 SPI,能够看一下jar包里面的META-INF/services目录,里面有一个java.sql.Driver的文件,文件里面包含了驱动的全路径名。咱们只须要下面这一句话就能够建立数据库链接:
Connection con = DriverManager.getConnection(url , username , password ) ;
由于类加载器受到加载范围的限制,在某些状况下父类加载器没法加载到所须要的文件,这时候就须要委托子类加载器去加载 class 文件。
JDBC 的 Driver 接口定义在 JDK 中,其实现由各个数据库服务商来提供,好比 MySQL 的驱动包。DriverManager 类中要加载各个实现了Driver接口的类,而后进行管理,可是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,根据类加载机制,当被装载的类引用了另一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。也就是说 Bootstrap 类加载器还要去加载 jar 包中的 Driver 接口的实现类。Bootstrap 只负责 /lib/rt.jar 里面全部的 class,因此须要子类加载器去加载 Driver,这就破坏了双亲委派模型。
查看 DriverManager 类的源码,看到使用 DriverManager 的时候会触发其静态代码块,调用 loadInitialDrivers() 方法,并调用 ServiceLoader.load(Driver.class) 加载全部在META-INF/services/java.sql.Driver 文件里边的类到JVM内存,完成驱动的自动加载。
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); }
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
这个子类加载器是经过 Thread.currentThread().getContextClassLoader() 获得的上下文加载器。
public Launcher() { ... try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); ... }
能够看到,在 sun.misc.Launcher 初始化的时候,会获取AppClassLoader,而后将其设置为上下文类加载器,因此线程上下文类加载器默认状况下就是系统加载器。
每一个 Tomcat 的 webappClassLoader 加载本身目录的 class 文件,不会传递给父类加载器。
事实上,Tomcat 之因此造出了一堆本身的 classLoader,大体是出于三个目的: