1.概述
- java虚拟机把描述类的数据从class文件加载到内存(放在运行时数据区的方法区内),并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
- ClassLoader只负责class文件的加载,至于他是否可以运行,由ExecutionEngine决定。
- 加载的类信息存放在运行时数据区的方法区内。除了类信息外,方法区还会存放常量池信息。
- 类的生命周期 加载、验证、准备、初始化、卸载这五个阶段顺序是确定的。而解析阶段则不一定。
2.类的加载时机
关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
1)遇到new、getstatic、putstatic或invokestat ic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
·使用new关键字实例化对象的时候。
·读取或设置一个类型的静态字段(被final修饰、己在编译期把结果放入常量池的静态字段除外)的时候。
·调用一个类型的静态方法的时候。
2〉使用java.lang reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。 3)当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
5)当使用JDK7新加入的动态语言支持时,如果一个java.lang invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStat ic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
6)当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
3.类的加载过程
1.Loading
完成以下三件事
-
通过一个类的全限定名来获取定义此类的二进制字节流。
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
-
Java类加载器由JVM提供,是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
-
类加载器,可以从不同来源加载类的二进制数据,比如:本地Class文件、Jar包Class文件、网络Class文件等等等。
-
类加载的最终产物就是位于堆中的类的java.langClass对象(注意不是目标类对象),该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口
2.Linking
(1)验证
- 目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。(修改字节码是指针越界)
- 主要包括四种验证
- 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
- 元数据验证:对字节码描述的信息进行语义分析保证不存在与《Java语言规范》定义相
悖的元数据信息。
- 字节码验证:主要目的是通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证:验证该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。
(2)准备
- 为类变量分配内存并且设置该类变量的默认初始值,即零值。
- 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;
- 这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
(3)解析
- 将常量池内的符号引用转换为直接引用的过程。
- 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。
- 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
- 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref_info等
3.Initialization
- 初始化阶段就是执行类构造器方法<clinit>()的过程
- 此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块(static{}块)中的语句合并而来。
- 构造器方法中指令按语句在源文件中出现的顺序执行。
- <clinit>()不同于类的构造器,它不需要显式地调用父类构造器,Java虚拟机会保证在子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行完毕。
-
<clinit>() 方法对于类或接口来说并 不是必需的 ,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成 <clinit>() 方法。
-
·Java 虚拟机必须保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁同步。
4.类加载器 (Class Loader)
-
类加载器 只 用于实现类的加载动作。
-
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。
-
站在Java虚拟机的角度来看,只存在 两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分;另外一种就是其他所有 的类加载器,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。
- 由C/C++实现,嵌套在JVM内部
- 用来加载java的核心库(<JAVA_HOME>\lib目录下的内容),用于提供JVM自身所需的类。
- 无父类加载器。
- 加载 扩展类和应用程序加载器,并指定为他们的父类加载器。
- 出于安全考虑,只加载java,javax,sun等开头的类。