本文这三章的笔记整理。java
类加载的过程能够简单分为三个阶段:sql
JVM
规范规定了每一个类或接口在首次主动使用的时候都须要进行初始化,规定了如下六种主动使用类的场景:数据库
new
关键字会致使类的初始化main()
的类)也会初始化除了以上六种状况外,其他的都叫被动使用,不会致使类的加载和初始化,好比引用类的静态常量不会致使类的初始化。编程
前面也说了类加载能够简单分为三个阶段:安全
下面先来看一下加载阶段。网络
加载阶段就是将class
文件中的二进制数据读取到内存之中,而后将该字节流表明的静态存储结构转换为方法区中运行时数据结构,而且在堆中生成一个该类的java.lang.Class
对象,做为访问方法区数据结构的入口。数据结构
类加载的最终产物就是堆内存中的class
对象,JVM
规范中指出类加载是经过一个全限定名去获取二进制数据流,来源包括:多线程
class
文件:这是最多见的格式,就是加载javac
编译后的字节码文件ASM
能够动态生成,或者能够经过动态代理java.lang.Proxy
生成等RMI
JAR
、WAR
包MySQL
中的BLOB
字段类型的数据class
文件而且动态加载:好比Thrift
、Avro
等序列化框架,将某个schema
生成若干个class
文件并进行加载类加载阶段结束后,JVM
会将这些二进制字节流按照JVM
定义的格式存放在方法区中,造成特定的数据结构后再在堆内存中实例化一个java.lang.Class
对象。架构
该阶段能够分为三个小阶段:并发
须要注意的是这三个小阶段其实不是顺序进行的,而是交叉着进行的,也就是解析的时候其实也会有验证的过程。
验证是为了确保字节流所包含的内容符合JVM
规范,而且不会出现危害JVM
自身安全的代码,当字节流信息不符合要求的时候,会抛出VerifyError
这样的异常或其子异常,验证的信息包括:
包括:
0xCAFEBABE
)元数据验证实际上是进行语义分析的过程,语义分析是为了确保字节流符合JVM
规范要求,包括:
final
的类字节码验证主要是验证程序的控制流程,包括:
验证符号引用转换为直接引用的合法性,保证解析动做的顺利执行,包括:
通过验证后,就开始了准备阶段,这阶段比较简单,就是对对象的静态变量分配内存而且设置初始值,类变量的内存会被分配到方法区中。设置初始值就是为相应的类变量给定一个相关类型在没有被设置时的默认值,好比Int
的初始值为0,引用的初始值为null
。
解析就是在常量池中寻找类、字段、接口和方法的符号引用,而且将这些符号引用替换成直接引用的过程。解析主要针对类接口、字段、类方法和接口方法进行的,包括:
初始化阶段主要就是执行<clinit>
方法的过程,该方法是编译阶段生成的,也就是说包含在字节码文件中,该方法包含了全部类变量的赋值动做和静态语句块的执行代码。另外一方面,<clinit>
与构造方法不一样,不须要显式调用父类构造器,虚拟机会保证父类的<clinit>
方法最早执行。
还须要注意的是<clinit>
只能被虚拟机执行,虚拟机还会保证多线程下的安全性,所以,若是在静态代码块中若是包含了加载其余类的操做可能会引发死锁,例子能够看这里。
JVM
中的三类核心类加载器JVM
中有三类核心类加载器,分别是:
C++
编写,负责JVM
核心类库的加载,好比加载整个java.lang
包中的类jre/lib/ext
子目录下的类库,纯Java
实现,是URLClassLoader
的子类classpath
下的类库,应用类加载器的父加载器为扩展类加载器,同时它也是自定义类加载器的默认父加载器一个类加载器加载一个类的时候,并不会尝试直接加载该类,而是先交给父加载器尝试加载,一直到顶层的父加载器(启动类加载器),若是父加载器加载失败,则会本身尝试加载,图示以下:
JDK
中提供了不少SPI
(Service Provider Interface
),好比JDBC
等,JDBC
只规定了这些接口之间的逻辑关系,但不提供具体的实现,换句话说,JDBC
彻底透明了应用程序和第三方厂商数据库驱动的具体实现,应用程序只须要面向接口编程便可。但问题是:
java.lang.sql
中的全部接口都是由JDK
提供的,加载这些接口的类加载器是启动类加载器因为双亲委派机制,Connections
、Statement
等都是由启动类加载器加载,而第三方JDBC
驱动包中的实现不会被加载。解决这个问题的关键,就是使用了线程上下文类加载器打破了双亲委派机制。
好比MySQL
驱动的加载过程,就是经过线程上下文类加载器加载的,
private static Connection getConnection(String url, Properties info, Class<?> caller) throws SQLException { //... if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) { callerCL = Thread.currentThread().getContextClassLoader(); } while(true) { //... if (isDriverAllowed(aDriver.driver, callerCL)) { } } //... } private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) { //... try { aClass = Class.forName(driver.getClass().getName(), true, classLoader); } catch (Exception var5) { result = false; } //... return result; }
经过线程上下文类加载器,就变成了启动类加载器去委托子类加载器去加载实现的方式,也就是JDK
本身亲自打破了双亲委派机制这种方式,这种加载方式几乎涉及全部的SPI
加载,包括JAXB
、JCE
、JBI
等。