03 解读 java类加载
分析java类加载过程
类加载主要是由jvm虚拟机负责的,过程就可以分为 编译 >>> 类加载 >>> 初始化
java类加载过程加载(loading)
是类加载 (Class Loading)
过程的一个阶段。类的加载其实就、是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态数据结构转化为方法区中运行的数据结构,并且在堆内存中生成一个java.lang.Class对象作为访问方法区数据结构的入口。
在加载阶段,虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
连接阶段
连接是很重要的一步,过程比较复杂,分为三步 验证 >>> 准备 >>> 解析
验证
验证
为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。不同的虚拟机对验证的实现可能会有所不同,基本都会存在:文件格式验证、元数据验证、字节码验证和符号引用验证。
-
文件格式验证:验证字节流是否符合Class文件格式的规范否,并且能被当前版本的虚拟机处理。后面三个验证阶段全部是基于方法区的存储结构进行的。
-
元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范要求。
- 类是否有父类(除了java.lang.Object之外,所有的类应当有父类)
- 类的父类是否继承了不允许被继承的类(被final修饰)
- 类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
- 类中的字段、方法是否与父类产生了矛盾
-
字节码验证:其主要是进行数据流和控制流分析。该阶段将对类的方法体进行校验分析。这阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。
- 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这种情况:在操作栈中放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中。
- 保证跳转指令不会跳转到方法体以外的字节码指令上。
- 保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,这是安全的,反之不合法。
-
符号引用验证:通常需要校验以下内容:
- 符号引用中通过字符串描述的全限定名是否能找到对应的类。
- 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
- 符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可被当前类访问。
- 符号引用验证的目的是确保解析动作能正常执行,如果无法通过符号引用验证,将会抛出一个
java.lang.IncompatibleClassChangeError
异常的子类,如java.lang.IllegalAccessError
、java.lang.NoSuchFieldError
、java.lang.NoSuchMethodError
等。
验证阶段对于虚拟机的类加载机制来说,是一个非常重要、但不一定是必要的阶段。如果所运行的全部代码都已经被反复使用和验证过,在实施阶段就可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,以锁单虚拟机类加载的时间。
准备
为类的静态变量分配内存,将其初始化为默认值 。我们都知道静态变量是可以不用我们手动赋值的,它自然会有一个初始值 比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的.
解析
把类中的符号引用转化为直接引用。解释一下符号引用和直接引用。比如在方法A中使用方法B,A(){B();},这里的B()就是符号引用,初学java时我们都是知道这是java的引用,以为B指向B方法的内存地址,但是这是不完整的,这里的B只是一个符号引用,它对于方法的调用没有太多的实际意义,可以这么认为,他就是给程序员看的一个标志,让程序员知道,这个方法可以这么调用,但是B方法实际调用时是通过一个指针指向B方法的内存地址,这个指针才是真正负责方法调用,他就是直接引用。
初始化
为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始值。
- 直到初始化才真正开始执行类中定义的Java程序代码。初始化阶段是执行类的构造器<clinit>()方法的过程。
类加载的内存分析
jvm内存模型类加载的内存分配,通过上面的三件事情知道类加载就是jvm虚拟机将类的.class文件加载到内存,并将它放到运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构(方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
)。
- 类被加载后,方法区会被分出一块内存,存储这个类的所有信息,但是这个内存块存储的依然是
.class
文件,类的信息被存储在方法区后,jvm虚拟机又会堆区创建一个java.lang.Class
对象,把方法区存储的类的结构全部反射过来,然后封装起来,成为了一个Class
类的对象。这个Class对象与对应的类是一一对应,它有类的结构信息,所以可以构造出一个类的对象。我们平时使用的对象就是由这个Class类的对象生成。到此,类的加载已经完成,但是此时依旧没有我们需要使用的对象产生。