JVM** 虚拟机**
ClassLoader ( 类加载器 )深入剖析
Java虚拟机与程序的生命周期
在如下几种情况下,Java虚拟机将结束生命周期
a). 执行了System.exit() 方法
b). 程序正常执行结束
c). 程序在执行过程中遇到了异常或错误而异常终止
d). 由于操作系统出现错误而导致Java虚拟机进程终止
类的 加载 、 连接 与 初始化
a). 加载:查找并加载类的二进制数据
b). 连接
b-1). **验证** :确保被加载的类的正确性 b-2). **准备** :为 **类** 的 **静态变量**** 分配内存 **,并将其** 初始化为默认值** b-3). **解析** :把类中的符号引用转换为直接引用c). 初始化:为类的静态变量赋予正确的初始值
Java 程序对类的使用方式可分为两种
- 主动使用
- 被动使用
所有的Java虚拟机实现必须在每个类或接口被Java程序" 首次主动使用"时才初始化他们.
主动使用 ( 六种 ):
-1). 创建类的实例 ( new Test(); )
-2). 访问某个类或接口的静态变量,或者对该静态变量赋值
Test.A; or Test.A = 1;-3). 调用类的静态方法
-4). 反射 ( 如:Class.forName("com.lhk.Test") 反射会自动生成一个类的实例 )
-5). 初始化一个类的子类
-6). Java虚拟机启动时被标明为启动类的类( JavaTest )
- 除了以上六种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化
类的加载
类的加载指的是将类的 .class文件中 的 二进制数据** 读入 到 内存中 ,将其放在 运行时数据区 的 方法区内,
然后在堆区 创建一个java.lang.Class对象* ,用来封装类在方法区内的数据结构。
加载 .class文件的方式
从本地系统中直接加载
通过网络加载 .class文件 (通过java提供的URLClassLoader类,可以实现)
从zip,jar等归档文件中加载 .class文件
从专有数据库中提取 .class文件
将java 源文件动态编译为.class 文件。
类的加载的最终产物是位于堆区中的Class对象
Class 对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问
方法区内的数据结构的接口。
类的加载过程:
有两种类型的加载器:
- - **java 虚拟机自带的加载器**
- **根类加载器** ( **Bootstrap** )
它是由 C++编写,程序员无法在Java代码中获得该类
**(一下两种使用Java编写)**
- **扩展类加载器** ( **Extension** )
- **系统类加载器** ( **System** ) :又称为 **应用加载器**
-- 用户自定义的类加载器
- lang.ClassLoader 的子类
用户可以定制类的加载方式
类加载器 并不需要 等到某个类被"首次主动使用"时再加载它
JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了
.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误( LinkageError 错误 )
如果这个类一直没有被程序主动使用,那么 类加载器就不会报告错误
类的连接过程:
1). **类的验证** :
类被加载后,就进入连接阶段。 **连接** 就是将已读入到内存的类的二进制数据
合并到虚拟机的运行时环境中去。
2). **类的验证的内容:**
--a). **类文件的结构检查:**
确保类文件遵从Java类文件的固定格式。
--b). **语义检查:**
确保类本身符合Java语言的语法规定,比如验证final类型的类没有子类,
以及final类型的方法没有被覆盖。
--c). **字节码验证:**
确保字节码流可以被Java虚拟机安全的执行。字节码流代表Java方法(包括静态方法和实例方法),它是由被称作操作码的单字节指令组成的序列,
每一个操作码后跟着一个或多个操作数。字节码验证步骤会检查每个操作码是否合法,即是否有着合法的操作数。

--d). **二进制兼容性的验证** :
确保相互引用的类之间协调一致。例如:在Worker类的gotoWork()方法中会调用Car
类的run()方法。Java虚拟机再验证Worker类时,会检查在方法区内是否存在Car类的run()
方法,假如不存在( 当Worker类和Car类的版本不兼容,就会出现这种问题 ),就会抛出
NoSuchMethodError错误。
3). **类的准备**
在准备阶段,Java 虚拟机为类的静态变量分配内存,并设置默认初始值。例如:对于以下
Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默
认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0。

4). **类的解析**
在解析阶段,Java虚拟机会把类的二进制数据中的符号引用替换为直接引用。
例如:在Worker类的gotoWork()方法中会引用Car类的run()方法。
在Worker类的二进制数据中,包含了一个队Car类的run()方法的符号引用,
它由run()方法的全名和相关描述符组成。在解析阶段,Java虚拟机会把这个符
号引用替换为一个指针,该指针指向Car类的run()方法在方法区内的内存位置,
这个指针就是直接引用。
类的初始化:
在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。
在程序中,静态变量的初始化有两重途径:
(1). 在静态变量的声明处进行初始化;
(2). 在静态代码块中进行初始化。
例如:在以下代码中,静态变量a 和 b 都被显式初始化;而静态变量c没有被
显式初始化,它将保持默认值0.

静态变量的声明语句,以及静态代码块都被看做类的初始化语句,Java虚拟机会
按照初始化语句在类文件中的先后顺序来依次执行它们。
例如:当一下Sample类被初始化后,它的静态变量a的取值为4。

类的初始化步骤 :
1). 假如这个类还没有被加载和连接,那就先进行加载和连接。
2). 假如类存在直接的父类,并且这个父类还没有被初始化,那就先初始化直接的父类
3). 假如类中存在初始化语句,那就依次执行这些初始化语句。
类的初始化时机:
**主动使用** ( **六种** )
-1). 创建类的实例
-2). 访问某个类或接口的静态变量,或者对该静态变量赋值
-3). 调用类的静态方法
-4). 反射 ( Class.forName("com.lhk.Test") )
-5). 初始化一个类的子类
- 6). Java虚拟机启动时被标明为启动类的类(Java Test)
**除了上述六种情形,其他使用Java类的方式都被看作是被动作用**** , ****不会导致类的初始化。**
-3). 当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适
用于接口
--- 在初始化一个类时,并不会先初始化它所实现的接口。
--- 在初始化一个接口时,并不会先初始化它所有实现的接口。
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定
接口的变量时,才会导致该接口的初始化。
**程序中对子类的 "主动使用"会导致父类被初始化;但对父类的"主动"使用并不会导致子类初始化**
(不可能说生成一个Object类的对象就导致系统中所有的子类都会被初始化)
**只有当前程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用**
调用 **ClassLoader** 类的 **loadClass** ()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
举例:
//第一步:获取系统的类加载器
**ClassLoader**** classLoader **= ** ClassLoader **.** getSystemClassLoader**();
//调用 **ClassLoader** 类的 **loadClass** ()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
Class<?> clazz = **classLoader**. **loadClass** ("com.jvm.classloader.one.C");
类加载器 – 父类委托机制
类加载器用来把类加载到Java虚拟机中。从JDK 1.2开始,类的加载过程采用 **父类委托机制**
这种机制能更好的保证Java平台的安全。在此委托机制中,除了Java虚拟机自带的根类加载器以外,
其余的类加载器都有且只有一个父类加载器 。 当Java程序请求加载器Loader1加载Sample类时,
Loader1首先委托自己的父类加载器去加载Sample类,若父类加载器能加载,则有父类加载器完成加载,
否则才有加载器Loader1加载Sample类。
Java 虚拟机自带了一下几种加载器:
* 根(Bootstrap)类加载:
该加载器没有父加载器。它负责加载虚拟机的核心类库,如:java.\*(lang,io,nio...).\*;是有根类加
载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现
依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。
* 扩展(Extension)类加载器:它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中
加载类库,或者从JDK的安装目录的jre\lib\ext子目录(扩展目录)下加载类库,如果把用户创建的jar文件
放在这个目录下,也会自动有扩展类加载器加载。扩展类加载器是纯Java类, 是** (这里说 是 子类,不一定是继承关系**)java.lang.ClassLoader类的子类。
* 系统(System) 类加载器:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath
或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父类加载器。系统类加载器是纯Java类,是(这里说**是 **子类,不一定是继承关系 )java.lang.ClassLoader类的子类。
———- 父类加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。
* 除了以上虚拟机自带的加载器以外,用户还可以定制自己的类加载器(User-defind Class Loader ).
Java 还提供了抽象类 java.lang.ClassLoader,所有用户自定义的类加载器应该继承ClassLoader类。
JVM 自带的类加载器之间的关系:(这种关系叫 委托机制 )
定义类加载器:
如果某个类加载器能够加载一个类,那么该类加载器就称作: **定义类加载器** ;
定义类加载器及其所有子加载器都称作: 初始类加载器
例如:loader1加载器加载了Sample类,那么loader1被称为定义类加载器。
那么loader1,和loader2 (loader1是loader2的父类加载器)叫做初始类加载器。
当生成一个自定义的类加载器时, 如果没有指定该类加载器的父类加载器 ,那么系统类加载器
就将称为该类的父类加载器。