一、虚拟机的类加载机制
虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
二、类的生命周期
三、我们首先从加载说起,什么情况才需要加载类?其实并没有约束,但是初始化有约束,而加载又必须在初始化之前执行。可以理解为变向约束了加载。
(1)、虚拟机规范严格规定了有且只有五种情况必须立即对类进行“初始化”:
1、使用new、getstatic、putstatic或invokestatic这4条字节码指令时,触发初始化。场景有遇到new关键字实例化对象的时候、读取或设置一个类的静态字段的时候,已经调用一个类的静态方法的时候。
2、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要先触发其初始化。
3、当初始化一个类的时候,如果发现其父类没有被初始化就会先初始化它的父类。
4、当虚拟机启动的时候,用户需要指定一个要执行的主类(就是包含main()方法的那个类),虚拟机会先初始化这个类;
5、使用Jdk1.7动态语言支持的时候的一些情况。
(2)在加载过程,虚拟机需要完成以下3件事情
1、通过类型的完全限定名,产生一个代表该类型的二进制数据流(根本没有指明从哪里获取、怎样获取,可以说一个非常开放的平台了;
2、解析这个二进制数据流为方法区内的运行时数据结构;
3、创建一个表示该类型的java.lang.Class类的实例,作为方法区这个类的各种数据的访问入口。
通过类型的完全限定名,产生一个代表该类型的二进制数据流的几种常见形式:
- 从zip包中读取,成为日后JAR、EAR、WAR格式的基础;
- 从网络中获取,这种场景最典型的应用就是Applet;
- 运行时计算生成,这种场景最常用的就是动态代理技术了;
- 由其他文件生成,比如我们的JSP;
注意:数组类本身不通过类加载器加载,它是由Java虚拟机直接创建的。但数组类的元数据类型有类加载器去创建。
加载完成后,虚拟机外部的二进制字节流就按照虚拟机所需要的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义,然后去内存中实例化一个java.lang.Class对象,存放在方法区中(并不是堆,比较特殊),这个对象作为程序访问方法区中的这些数据的外部接口。
四、加载过后,就是验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
虚拟机如果不检查输入的字节流,并对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作。这个阶段是否严谨,直接决定了java虚拟机是否能承受恶意代码的攻击。
从整体上看,验证阶段大致上会完成4个阶段的校验工作:
文件格式验证:字节流是否符合Class文件格式的规范
元数据验证:对字节码描述的信息进行语义分析,以保证信息符合Java语言规范
字节码验证:确认语义是否合法,符合逻辑,是否有攻击性
符号引用验证
五、准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。(备注:这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中)。
初始值通常是数据类型的零值:
对于:public static int value = 123,那么变量value在准备阶段过后的初始值为0而不是123,这时候尚未开始执行任何java方法,把value赋值为123的动作将在初始化阶段才会被执行。一些特殊情况:
对于:public static final int value = 123; 编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。
六、解析
1、解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。首先说明这两个概念:
(1)符号引用(Symbolic References): 符号引用以一组符号来描述所引用的目标,符号可以是符合约定的任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
(2)直接引用(Direct References): 直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用与虚拟机实现的内存布局相关,引用的目标必定已经在内存中存在。
虚拟机规范没有规定解析阶段发生的具体时间,虚拟机实现可以根据需要(16个字节码指令)来判断到底是在类被加载时解析还是等到一个符号引用将要被使用前才去解析。
2、对解析结果进行缓存
同一符号引用进行多次解析请求是很常见的,除invokedynamic指令(用于动态语言支持)以外,虚拟机实现可以对第一次解析结果进行缓存,来避免解析动作重复进行。
3、解析动作对象
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。前面四种引用的解析过程,对于后面三种,与JDK1.7新增的动态语言支持息息相关,由于java语言是一门静态类型语言,因此没有介绍invokedynamic指令的语义之前,没有办法将他们和现在的java语言对应上。
七、初始化
类初始化阶段是类加载的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的java程序代码。
初始化阶段是执行类构造器<clinit>( )方法的过程;
借鉴:《深入理解Java虚拟机》
博客:https://blog.youkuaiyun.com/qq_34337272/article/details/80265210