JVM类加载机制,java类的加载时机
一、什么是类加载机制
在说类加载机制之前,先看下一张jvm加载类的大致流程图,方便理解。
由上图可以看出,java文件通过编辑器编译生成了class文件,接下来的 类装载器 环节,就是我们所说的 类加载机制。
类加载机制 其实就是: 首先通过 类加载器 将class文件加载到内存中,然后对其进行 验证、准备、解析、初始化,使这些数据最终成为可以被JVM直接使用的Java类型,这个过程就是 JVM类加载机制。
1、什么时候进行类的加载
什么时候进行类的 加载 ?相信很多人在面试的时候都遇到过这个问题。
我们平常所说类的加载 主要指的是 类加载机制 的第一个步骤。类加载机制 包括了 加载、验证、准备、解析、初始化这几个步骤。当然还有最后一个步骤 卸载。关于这几个步骤,后面会详述。
当应用程序启动的时候,所有的类会被一起加载吗?当然不可能,毕竟系统的内存资源是有限的。那什么时候才会被加载呢?
一般某个类 首次主动使用 的时候会被加载。类的 加载 是类加载机制 的第一个步骤,相对其他几个步骤,它是可控性最强的一个阶段,因为我们可以使用系统的类加载器加载,也可以使用自己自定义的类加载器进行加载。同时我们还可以进行 预加载,不需要等到某个类 首次主动使用 的时候再去加载。当然,如果 预加载 过程中出现了异常,类加载器必须在程序首次主动使用该类的时候才抛出异常,如果该类一直没有被主动使用,类加载器就不会抛出异常。
2、从哪个地方加载
加载的地方很多,比如:
- 本地磁盘
- 网络上加载
- 从其他文件生成的
二、类的生命周期
类从加载到虚拟机内存开始,到从虚拟机中卸载结束一共包含了 七个生命周期阶段:加载、验证、准备、解析、初始化、使用、卸载。类的加载机制包含了前5个阶段,如下图:
链接 分为 验证、准备、解析 三个步骤。
1、加载
- 将class文件加载在内存中。这里就是通过类加载机制来加载的。详细请看博客:https://blog.csdn.net/tongsiw/article/details/79939663
- 将静态存储结构(数据存在于class文件的结构)转化为方法区的运行时数据结构(数据存在于JVM时的数据结构)。这里只是转化了数据结构,并未合并数据。(方法区就是用来存放已被加载的类信息,常量,静态变量,编译后的代码的运行时内存区域)。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。这个Class对象并没有规定是在Java堆内存中,它比较特殊,虽为对象,但存放在方法区中。
2、链接
链接就是将Java类的二进制代码数据合并到Java运行时环境中。类的链接大致分三个阶段:
- 验证: 确保加载的类符合JVM规范与安全
-
准备: 为类的 静态变量(static filed) 在方法区分配内存,并赋默认初值(0值或null值)。如 static int a = 1 ,静态变量 a 会在 准备 阶段 被赋默认值0。
静态常量(static final filed) 会被直接赋值。如 static final int b = 1,静态常量 b 会在准备阶段北直接赋值1。 - 解析: 虚拟机将常量池的符号引用转变成直接引用。
3、初始化
这是类加载机制的最后一步,在这个阶段,java程序代码才开始真正执行。我们知道,在准备阶段,会给静态变量 赋初始值,在初始化阶段,就是赋真正的值了,如上 static int a = 1 ,静态变量 a 在 准备 阶段被被赋默认值0,在初始化阶段就会被赋值1。
初始化阶段总结一句话就是:对类变量赋予正确的值。
初始化一个类,包含两个步骤:
- 如果存在父类,先初始化父类。
- 如果类中有初始化语句,则系统依次执行这些初始化语句。
**初始化时机:**只有当类被主动引用的时候,才会被初始化。
类的主动引用:
- new一个对象。
- 调用类的静态成员(除了final常量)和静态方法。
- 通过反射(如 Class.forName(“com.xx.Xx”))对类进行调用。
- 虚拟机启动,main方法所在类被提前初始化。
- 初始化一个类,如果其父类没有初始化,则先初始化父类。
类的被动引用:
除了上面的几种类的主动引用方式,剩下的就是 类的被动应用。类的被动应用不会触发类的初始化。
根据类的主动引用那几种方式,类的被动引用有以下几个实例:
- new一个对象数组,注意是对象数组而不是对象,如 Object obj = new Object[10],这个时候也不会触发类的初始化。
- 调用一个类中final修饰的常量,是不会触发 类的初始化的。
- 通过子类引用父类的静态字段,父类会被初始化,子类是不会触发初始化的
看如下代码示例:
父类
public class Father {
public static int value = 1;
public static final int finalValue = 2;
static {
System.out.println("father init");
}
}
子类
public class Son extends Father {
static {
System.out.println("son init");
}
}
测试类
public class Test {
public static void main(String[] args) {
System.out.println("开始验证new一个对象数组,不会触发类的初始化");
Father[] fathers = new Father[10];
System.out.println("\n\n开始验证调用一个类的final修饰常量,不会触发类的初始化");
System.out.println(Son.finalValue);
System.out.println("\n\n开始验证通过子类引用父类的静态字段,父类会被初始化,子类是不会触发初始化");
System.out.println(Son.value);
}
}
打印结果
开始验证new一个对象数组,不会触发类的初始化
开始验证调用一个类的final修饰常量,不会触发类的初始化
2
开始验证通过子类引用父类的静态字段,父类会被初始化,子类是不会触发初始化
father init
1
可以看出 new Father[10]数组是不会触发Father类初始化的,因为没有打印出father init 这条语句。
同理,调用一个类的final修饰常量,也是不会触发类的初始化的。
4、使用
初始化之后,jvm开始执行用户的程序代码
5、卸载
当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后运行的 JVM 也退出内存。
//TODO
这里类加载机制中 类的初始化和类的实例化 有什么区别呢?
java new 之后具体还有哪些过程?Java对象具体的创建过程是什么?
本文地址:https://blog.csdn.net/tongsiw/article/details/108029325