加载
在加载阶段,虚拟机需要完成以下三件事:
1、通过类的全限定名来获取定义此类的二进制字节流。但规范并没有指明二进制字节流要从一个Class文件中获取,所以,在Java的发展历史中出现了很多字节流的提供方式:zip包,网络(例如applet),动态代理技术,其他文件(如jsp);
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
相对于类加载过程的其他阶段来说,加载阶段(准确的说,是在加载阶段中获取类的二进制字节流的动作)是开发期可控性最强的阶段,既可以使用系统提供的类加载器完成,也可由用户自定义的类加载器来完成。
加载阶段与连接阶段的部分内容(如一部分字节码文件格式的验证动作)是交叉进行的,加载动作尚未完成,连接阶段可能已经开始。
验证
目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
包含以下几个方面的验证工作:
1、文件格式验证;
2、元数据验证(确保不存在不符合Java语言规范的元数据信息存在,比如是否覆盖了父类中被final修饰的属性等);
3、字节码验证。主要是进行数据流和控制流分析;
4、符号引用验证。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。并且,此时分配的初始值是指该数据类型的零值。假设有一个类变量的定义为
public static int aStaticVariable = 123;
则在准备阶段过后该变量的初始值为0,而非123,因为此时并没有执行任何Java方法。而把aStaticVariable赋值为123的putstatic指令是在程序被编译后,存放在类的构造器<clinit>()方法之中,所以把aStaticVariable赋值为123的动作将发生在初始化阶段。但如果aStaticVariable是经过final修饰的,那么在准备阶段就会直接将其赋值123。
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用是能无其一的定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
直接引用:可以是直接指向目标的指针、相对偏移量或者是一个能直接定位到目标的句柄。直接引用是虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实力上翻译出来的直接引用一般不会相同。如果有了直接引用,说明引用的目标已经存在内存中存在。
虚拟机规范并没有规定解析阶段发生的具体时间,只是要求了在执行anewarray, checkcast, getfield, getstatic, instanceof, invokeinterface, invokespecial, invokestatic, invokevirtual, multianewarray, new, putfield和putstatic这13个用于操作符号引用的字节码指令之前,先对它们所使用的符号引用进行解析。
虚拟机实现可能会对同一个符号引用的解析结果进行缓存,以提高响应效率。
解析动作主要针对类或接口(对应常量池的CONSTANT_Class_info)、字段(CONSTANT_Fieldref_info)、类方法(CONSTANT_Methodref_info)及接口方法(CONSTANT_InterfaceMethodref_info)四类符号引用进行。
初始化
类的初始化阶段是类加载过程的最后一步。到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。
前面提到,在准备阶段,虚拟机已经为变量赋值过一次初始值(初始零值,如果是经过final修饰,则直接按照代码赋值),而在初始化阶段,则是根据程序的定义进行类变量和static语句块的初始化工作。换做更专业的说法,类的初始化阶段是执行类构造器<clinit>()方法的过程。
- <clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{})块中的语句合并产生的。编译器收集的顺序一定是先变量赋值,再静态语句块(无论两者在源文件中出现的顺序如何),因此,在静态语句块中可以访问到类变量的初始值。
- <clinit>()方法与类的构造函数(或者说实例构造器<init>())方法不同,它不需要显式调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此,在虚拟机中第一个被执行的<clinit>()方法的类一定是java.lang.Object。
- 由于父类的<clinit>()方法先于子类执行,所以父类中的静态语句块要优先于子类的变量赋值操作。
- <clinit>()方法并不是必须的,如果类或接口中没有类变量的赋值或者静态语句块,则编译器可以不为这个类生成<clinit>()方法。
- 接口中不能使用静态语句块,但可以有静态变量,因此接口和类一样都会生成<clinit>()方法。但不同的是接口中的<clinit>()不需要先执行父接口的<clinit>(),只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程被阻塞。所以,如果在<clinit>()方法中存在耗时操作,会形成很隐蔽的阻塞。
相关推荐
经典的java虚拟机类加载机制 看完后会有醍醐灌顶的感觉
作者:【郭孝星】http://blog.csdn.net/allenwells 微博:【郭孝星的新浪微博】http://weibo.com/allenwells 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells Github:...
什么是虚拟机类加载机制以及加载过程,以及类加载时机
Java虚拟机类加载机制及双亲委派模型
虚拟机把描述类的数据从Class文件中加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,这就是虚拟机加载机制。
虚拟机的加载机制是指将描述类的数据和文件从class文件加载到内存中,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的Java类型。类加载的生命周期包括加载、验证、准备、解析、初始化、使用...
虚拟机将描述类的数据从Class文件加载到内存,并对数据进行校验、准备、解析和初始化,终会形成可以被虚拟机使用的Java类型,这是一个虚拟机的类加载机制。Java中的类是动态加载的,只有在运行期间使用到该类的...
Java类加载机制是Java程序运行的第一步,它对于理解Java虚拟机(JVM)的行为至关重要。类加载过程涉及到类的加载、链接(验证、准备、解析)、初始化等阶段,并且这一过程是由类加载器系统完成的。 #### 二、类加载...
Android虚拟机&Android类加载机制 Android虚拟机&Android类加载机制 Android虚拟机&Android类加载机制
思维导图
总结,JVM 类加载机制是Java平台的核心特性之一,它确保了程序的稳定运行和动态扩展能力。理解类加载器的工作原理和双亲委派模型对于优化程序性能、解决类冲突以及构建复杂的模块化系统至关重要。在实际开发中,掌握...
Java的类加载机制遵循双亲委派模型,即当一个类加载器收到加载类的请求时,它首先会委托父类加载器去尝试加载,只有当父类加载器无法加载时,当前类加载器才会尝试自己加载。这种模型保证了Java核心库类的一致性和...
### Java 类加载机制详解 ...总结而言,Java 类加载机制是一个复杂而精细的过程,它不仅确保了Java程序的安全性和可移植性,还提供了强大的灵活性。理解类加载机制对于开发高效稳定的Java应用程序至关重要。
类加载机制的过程可以分为三个阶段:加载、链接和初始化。 加载阶段:在加载阶段,Java虚拟机将.class文件加载到内存中,并将其转换为机器可执行的代码。加载阶段是类加载机制的第一阶段。 链接阶段:在链接阶段,...
在《Java虚拟机类加载机制》一文中详细阐述了类加载的过程,并举了几个例子进行了简要分析,在文章的后留了一个悬念给各位,这里来揭开这个悬念。建议先看完《Java虚拟机类加载机制》这篇再来看这个,印象会比较深刻...
在这个过程中,类加载器主要任务是根据类的全限定名加载二进制字节流并转化为`java.lang.Class`对象。整个加载过程包括加载(Loading)、连接(Linking)和初始化(Initialization)三个步骤。 加载阶段是类加载的...
Java 动态类加载机制研究及应用是基于 Java 虚拟机(JV M)机制的,旨在实现 Java 应用程序中动态加载类文件,而不影响其他功能模块的正常运行。为了实现这个目标,需要对 Java 类加载器的体系结构、动态类加载机制...
本篇主要基于“译 Java类加载机制(一、二)”的博客内容,深入探讨Java的类加载过程、类加载器以及双亲委派模型。 首先,理解类加载机制的三个基本过程:加载、验证、准备、解析和初始化。加载是找到类的字节码并...