① 类加载过程
我们可以看出整个JVM执⾏的流程中,和程序员关系最密切的就是类加载的过程了,所 以接下来我们来看下类加载的执⾏流程。 对于⼀个类来说,它的⽣命周期是这样的:

其中前5步是固定的顺序并且也是类加载的过程,其中中间的3步我们都属于连接,所以对于类加载 来说总共分为以下⼏个步骤:
-
加载
-
连接
a. 验证
b. 准备
c. 解析
- 初始化
下⾯我们将分别来看每个步骤的具体执⾏内容。
1) 加载
"加载"(Loading)阶段是整个"类加载"(ClassLoading)过程中的⼀个阶段,它和类加载 ClassLoading是不同的,⼀个是加载Loading另⼀个是类加载ClassLoading,所以不要把⼆者搞混 了。
在加载Loading阶段,Java虚拟机需要完成以下三件事情:
1)通过⼀个类的全限定名来获取定义此类的⼆进制字节流。
2)将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构。
3)在内存中⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类的各种数据的访问⼊⼝。
2) 验证
验证是连接阶段的第⼀步,这⼀阶段的⽬的是确保Class⽂件的字节流中包含的信息符合《Java虚拟机 规范》的全部约束要求,保证这些信息被当作代码运⾏后不会危害虚拟机⾃⾝的安全。 验证选项:
• ⽂件格式验证
• 字节码验证
• 符号引⽤验证...
3) 准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值 的阶段。 ⽐如此时有这样⼀⾏代码:
publicstaticintvalue=123;
它是初始化value的int值为0,⽽⾮123。
4) 解析
解析阶段是Java虚拟机将常量池内的符号引⽤替换为直接引⽤的过程,也就是初始化常量的过程。
5) 初始化
初始化阶段,Java虚拟机真正开始执⾏类中编写的Java程序代码,将主导权移交给应⽤程序。初始 化阶段就是执⾏类构造器⽅法的过程。
② 双亲委派模型
提到类加载机制,不得不提的⼀个概念就是"双亲委派模型"。
站在Java虚拟机的⻆度来看,只存在两种不同的类加载器:⼀种是启动类加载器(Bootstrap ClassLoader),这个类加载器使⽤C++语⾔实现,是虚拟机⾃⾝的⼀部分;另外⼀种就是其他所有的 类加载器,这些类加载器都由Java语⾔实现,独⽴存在于虚拟机外部,并且全都继承⾃抽象类 java.lang.ClassLoader。
站在Java开发⼈员的⻆度来看,类加载器就应当划分得更细致⼀些。⾃JDK1.2以来,Java⼀直保 持着三层类加载器、双亲委派的类加载架构器。
什么是双亲委派模型?
如果⼀个类加载器收到了类加载的请求,它⾸先不会⾃⼰去尝试加载这个类,⽽是把这个请求委派给 ⽗类加载器去完成,每⼀个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层 的启动类加载器中,只有当⽗加载器反馈⾃⼰⽆法完成这个加载请求(它的搜索范围中没有找到所需 的类)时,⼦加载器才会尝试⾃⼰去完成加载。


• 启动类加载器:加载JDK中lib⽬录中Java的核⼼类库,即$JAVA_HOME/lib⽬录。扩展类加载 器。加载lib/ext⽬录下的类。
• 应⽤程序类加载器:加载我们写的应⽤程序。
• ⾃定义类加载器:根据⾃⼰的需求定制类加载器。
双亲委派模型的优点
1. 避免重复加载类:⽐如A类和B类都有⼀个⽗类C类,那么当A启动时就会将C类加载起来,那 么在B类进⾏加载时就不需要在重复加载C类了。
2. 安全性:使⽤双亲委派模型也可以保证了Java的核⼼API不被篡改,如果没有使⽤双亲委派模 型,⽽是每个类加载器加载⾃⼰的话就会出现⼀些问题,⽐如我们编写⼀个称为java.lang.Object 类的话,那么程序运⾏的时候,系统就会出现多个不同的Object类,⽽有些Object类⼜是⽤⼾⾃ ⼰提供的因此安全性就不能得到保证了。
③ 破坏双亲委派模型
双亲委派模型虽然有其优点,但在某些情况下也存在⼀定的问题,⽐如Java中SPI(Service ProviderInterface,服务提供接⼝)机制中的JDBC实现。
⼩知识:SPI全称ServiceProviderInterface,是Java提供的⼀套⽤来被第三⽅实现或者扩展的接 ⼝,它可以⽤来启⽤框架扩展和替换组件。SPI的作⽤就是为这些被扩展的API寻找服务实现。
JDBC的Driver接⼝定义在JDK中,其实现由各个数据库的服务商来提供,⽐如MySQL驱动包。我 们先来看下JDBC的核⼼使⽤代码:
java
public class JdbcTest {
public static void main(String[] args){
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:330
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println(connection.getClass().getClassLoader());
System.out.println(Thread.currentThread().getContextClassLoader());
System.out.println(Connection.class.getClassLoader());
}
}
然后我们进⼊DriverManager的源码类就会发现它是存在系统的rt.jar中的,如下图所⽰:

由双亲委派模型的加载流程可知rt.jar是有顶级⽗类BootstrapClassLoader加载的,如下图所⽰:

⽽当我们进⼊它的getConnection源码是却发现,它在调⽤具体的类实现时,使⽤的是⼦类加载器 (线程上下⽂加载器Thread.currentThread().getContextClassLoader)来加载具体的数据库数据库 包(如mysql的jar包),源码如下:
java
@CallerSensitive
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLExcept
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
//获取线程上下为类加载器
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// isDriverAllowed 对于 mysql 连接 jar 进⾏加载
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName())
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getC
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
这样⼀来就破坏了双亲委派模型,因为DriverManager位于rt.jar包,由BootStrap类加载器加载, ⽽其Driver接⼝的实现类是位于服务商提供的Jar包中,是由⼦类加载器(线程上下⽂加载器 Thread.currentThread().getContextClassLoader)来加载的,这样就破坏了双亲委派模型了(双亲 委派模型讲的是所有类都应该交给⽗类来加载,但JDBC显然并不能这样实现)。它的交互流程图如 下所⽰:
