Class文件是各类编译器编译生成的二进制文件,在Class文件中描述了各类与该类相关的信息,可是Class文件自己是一个静态的东西,想要使用某个类的话,须要java虚拟机将该类对应的Class文件加载进虚拟机中以后才能进行运行和使用。java
举个例子,Class文件就比如是各个玩具设计商提供的设计方案,这些方案自己是不能直接给小朋友玩的,须要玩具生产商根据方案的相关信息制造出具体的玩具才能够给小朋友玩。那么不一样的设计商有他们本身的设计思路,只要最终设计出来的方案符合生产商生产的要求便可。生产商在生产玩具时,首先会根据本身的生产标准对设计商提交来的方案进行阅读,审核,校验等一系列步骤,若是该方案符合生产标准,则会根据方案建立出对应的模具,当经销商须要某个玩具时,生产商则拿出对应的模具生产出具体的玩具,而后把玩具提交给经销商。数组
对于java而言,虚拟机就是玩具生产商,设计商提交过来的方案就是一个个的Class文件,方案建立的模具就 总的来讲,类的加载过程,包括卸载在内的整个生命周期共有如下7个阶段:安全
加载、验证、准备、初始化、卸载这5个阶段的顺序是肯定的,可是解析阶段不必定,在某些状况下解析能够在初始化以后再执行,为了支持java的运行时绑定,也成为动态绑定或晚期绑定。invokedynamic指令就是用于动态语言支持,这里“动态”的含义是必须等到城市实际运行到这条指令的时候,解析动做才开始执行。bash
“加载”是“类加载”过程当中的一个阶段,在加载阶段,虚拟机须要作如下3件事情:数据结构
经过类的全限定名得到该类的二进制字节流多线程
将这个字节流所表明的静态存储结构转换成方法区中的某个运行时数据结构spa
在方法区内存(对于HotSpot虚拟机)中生成一个表明该类的java.lang.Class对象,做为访问方法区中该类的运行时数据结构的外部接口线程
加载阶段中“经过类的全限定名得到该类的二进制字节流”这个动做,被放到java虚拟机外部实现,目的是最大限度的让应用程序去决定该如何获取所需的类,而实现该动做的代码模块就是类加载器(ClassLoader),JVM提供了3种类加载器:设计
启动类加载器(Bootstrap ClassLoader):负责加载 JAVAHOME\lib 目录中的,或经过-Xbootclasspath参数指定路径中的,且被虚拟机承认(按文件名识别,如rt.jar)的类。指针
扩展类加载器(Extension ClassLoader):负责加载 JAVAHOME\lib\ext 目录中的,或经过java.ext.dirs系统变量指定路径中的类库。
应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中了。
加载完成后,紧接着(更确切的说是交叉执行)虚拟机会对加载的字节流进行验证。虚拟机若是不检查输入的字节流,对其安全信任的话,极可能会由于载入了有害的字节流而致使系统崩溃。验证阶段大体会完成4中不一样的检验动做:
文件格式验证
文件格式验证主要是校验该字节流是否符合Class文件格式的规范,而且能被当前版本的虚拟机所接受。这个阶段包括但不限于如下验证点:
元数据验证
元数据验证主要是对字节流中的描述信息(描述符)进行语义分析,以确保其描述的信息符合java语言规范的要求。这个阶段包括但不限于如下验证点:
字节码验证
字节码验证主要是对类的方法体进行分析,确保在方法运行时不会有危害虚拟机的事件发生。这个阶段包括但不限于如下验证点:
符号引用验证
符号引用验证主要是对类自身之外的信息进行匹配新校验,包括常量池中的各类符号引用。这个阶段包括但不限于如下验证点:
准备阶段是为类变量(static)在方法区中分配内存并设置初始值(默认值,如int的默认值为0)的阶段。实例变量将会在对象实例化时随着对象一块儿分配在java堆中。为类变量设置初始值跟该变量是否有final修饰符有关系。 若是没用final进行修饰,以下列的代码:
// 准备阶段执行完成后,value变量的值为int的“零值”,即:0
// 把value赋值为10的putstatic指令是程序被编译后,
// 存放于类构造器<clinit>()方法中的,因此具体赋值的操做会在初始化阶段执行
public static int value = 10;
复制代码
若是使用了final进行修饰,以下列的代码:
// 若是类字段的字段属性表中有ConstantValue属性,
// 则会在准备阶段将变量的值初始化为ConstantValue所指定的值
// 准备阶段执行完成后,VALUE变量的值被赋值为20
public final static int VALUE = 20;
复制代码
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程,在该阶段将会进行符号引用的校验。 符号引用是Class文件中用来描述所引用目标的符号,能够是任何形式的字面量。 直接引用是虚拟机在内存中引用具体类或接口的,能够是直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄。 简单的来讲,符号引用是Class类文件用来定位目标的,直接引用是虚拟机用来在内存中定位目标的。
初始化阶段是执行类的构造器方法()的过程,类的构造方法由类变量的赋值动做和静态语句块按照在源文件中出现的顺序合并而成,该合并操做由编译器完成。
java虚拟机规范严格规定了有且只有一下5中状况必须当即对类进行初始化:
如下几种状况,不会触发类初始化:
class Parent {
static int a = 100;
static {
System.out.println("parent init!");
}
}
class Child extends Parent {
static {
System.out.println("child init!");
}
}
public class Init{
public static void main(String[] args){
// 只会执行父类的初始化,不会执行子类的初始化
// 将打印:parent init!
System.out.println(Child.a);
}
}
复制代码
public class Init{
public static void main(String[] args){
// 不会有任何输出
Parent[] parents = new Parent[10];
}
}
复制代码
class Const {
static final int A = 100;
static {
System.out.println("Const init");
}
}
public class Init{
public static void main(String[] args){
// Const.A会存入Init类的常量池中,调用时并不会触发Const类的初始化
// 将打印:100
System.out.println(Const.A);
}
}
复制代码
class Cat {
private string name;
static {
System.out.println("Cat is loaded");
}
}
public class Init{
public static void main(String[] args){
// 不会打印任何信息
Class catClazz = Class.class;
}
}
复制代码
class Cat {
private string name;
static {
System.out.println("Cat is loaded");
}
}
public class Init{
public static void main(String[] args) throws ClassNotFoundException{
// 不会打印任何信息
Class catClazz = Class.forName("com.test.Cat",false,Cat.class.getClassLoader());
}
}
复制代码
class Cat {
private string name;
static {
System.out.println("Cat is loaded");
}
}
public class Init{
public static void main(String[] args) throws ClassNotFoundException{
// 不会打印任何信息
new ClassLoader(){}.loadClass("com.test.Cat");
}
}
复制代码
最后,附上一幅Class类加载过程的思惟导图:
是Class文件加载进虚拟机中的类,生产的玩具就是类的实例对象。
所以,从Class文件到对象须要通过的步骤大体为: Class文件-->类-->实例对象 而类的加载机制,就是负责将Class文件转换成虚拟机中的类的一个过程。
总的来讲,类的加载过程,包括卸载在内的整个生命周期共有如下7个阶段:
加载、验证、准备、初始化、卸载这5个阶段的顺序是肯定的,可是解析阶段不必定,在某些状况下解析能够在初始化以后再执行,为了支持java的运行时绑定,也成为动态绑定或晚期绑定。invokedynamic指令就是用于动态语言支持,这里“动态”的含义是必须等到城市实际运行到这条指令的时候,解析动做才开始执行。
“加载”是“类加载”过程当中的一个阶段,在加载阶段,虚拟机须要作如下3件事情:
经过类的全限定名得到该类的二进制字节流
将这个字节流所表明的静态存储结构转换成方法区中的某个运行时数据结构
在方法区内存(对于HotSpot虚拟机)中生成一个表明该类的java.lang.Class对象,做为访问方法区中该类的运行时数据结构的外部接口
加载阶段中“经过类的全限定名得到该类的二进制字节流”这个动做,被放到java虚拟机外部实现,目的是最大限度的让应用程序去决定该如何获取所需的类,而实现该动做的代码模块就是类加载器(ClassLoader),JVM提供了3种类加载器:
启动类加载器(Bootstrap ClassLoader):负责加载 JAVAHOME\lib 目录中的,或经过-Xbootclasspath参数指定路径中的,且被虚拟机承认(按文件名识别,如rt.jar)的类。
扩展类加载器(Extension ClassLoader):负责加载 JAVAHOME\lib\ext 目录中的,或经过java.ext.dirs系统变量指定路径中的类库。
应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中了。
加载完成后,紧接着(更确切的说是交叉执行)虚拟机会对加载的字节流进行验证。虚拟机若是不检查输入的字节流,对其安全信任的话,极可能会由于载入了有害的字节流而致使系统崩溃。验证阶段大体会完成4中不一样的检验动做:
文件格式验证
文件格式验证主要是校验该字节流是否符合Class文件格式的规范,而且能被当前版本的虚拟机所接受。这个阶段包括但不限于如下验证点:
元数据验证
元数据验证主要是对字节流中的描述信息(描述符)进行语义分析,以确保其描述的信息符合java语言规范的要求。这个阶段包括但不限于如下验证点:
字节码验证
字节码验证主要是对类的方法体进行分析,确保在方法运行时不会有危害虚拟机的事件发生。这个阶段包括但不限于如下验证点:
符号引用验证
符号引用验证主要是对类自身之外的信息进行匹配新校验,包括常量池中的各类符号引用。这个阶段包括但不限于如下验证点:
准备阶段是为类变量(static)在方法区中分配内存并设置初始值(默认值,如int的默认值为0)的阶段。实例变量将会在对象实例化时随着对象一块儿分配在java堆中。为类变量设置初始值跟该变量是否有final修饰符有关系。 若是没用final进行修饰,以下列的代码:
// 准备阶段执行完成后,value变量的值为int的“零值”,即:0
// 把value赋值为10的putstatic指令是程序被编译后,
// 存放于类构造器<clinit>()方法中的,因此具体赋值的操做会在初始化阶段执行
public static int value = 10;
复制代码
若是使用了final进行修饰,以下列的代码:
// 若是类字段的字段属性表中有ConstantValue属性,
// 则会在准备阶段将变量的值初始化为ConstantValue所指定的值
// 准备阶段执行完成后,VALUE变量的值被赋值为20
public final static int VALUE = 20;
复制代码
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程,在该阶段将会进行符号引用的校验。 符号引用是Class文件中用来描述所引用目标的符号,能够是任何形式的字面量。 直接引用是虚拟机在内存中引用具体类或接口的,能够是直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄。 简单的来讲,符号引用是Class类文件用来定位目标的,直接引用是虚拟机用来在内存中定位目标的。
初始化阶段是执行类的构造器方法()的过程,类的构造方法由类变量的赋值动做和静态语句块按照在源文件中出现的顺序合并而成,该合并操做由编译器完成。
java虚拟机规范严格规定了有且只有一下5中状况必须当即对类进行初始化:
如下几种状况,不会触发类初始化:
class Parent {
static int a = 100;
static {
System.out.println("parent init!");
}
}
class Child extends Parent {
static {
System.out.println("child init!");
}
}
public class Init{
public static void main(String[] args){
// 只会执行父类的初始化,不会执行子类的初始化
// 将打印:parent init!
System.out.println(Child.a);
}
}
复制代码
public class Init{
public static void main(String[] args){
// 不会有任何输出
Parent[] parents = new Parent[10];
}
}
复制代码
class Const {
static final int A = 100;
static {
System.out.println("Const init");
}
}
public class Init{
public static void main(String[] args){
// Const.A会存入Init类的常量池中,调用时并不会触发Const类的初始化
// 将打印:100
System.out.println(Const.A);
}
}
复制代码
class Cat {
private string name;
static {
System.out.println("Cat is loaded");
}
}
public class Init{
public static void main(String[] args){
// 不会打印任何信息
Class catClazz = Class.class;
}
}
复制代码
class Cat {
private string name;
static {
System.out.println("Cat is loaded");
}
}
public class Init{
public static void main(String[] args) throws ClassNotFoundException{
// 不会打印任何信息
Class catClazz = Class.forName("com.test.Cat",false,Cat.class.getClassLoader());
}
}
复制代码
class Cat {
private string name;
static {
System.out.println("Cat is loaded");
}
}
public class Init{
public static void main(String[] args) throws ClassNotFoundException{
// 不会打印任何信息
new ClassLoader(){}.loadClass("com.test.Cat");
}
}
复制代码
最后,附上一幅Class类加载过程的思惟导图: