什么是类加载机制
Java虚拟机在把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程称作虚拟机的类加载机制。
类加载的时机
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,他的整个生命周期将会经历以下7个阶段:
- 加载(Loading)
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
- 使用(Using)
- 卸载(Unloading)
验证、准备、解析 3 个阶段统称为连接。
加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化之后再开始,这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。
类加载的过程
加载
在加载阶段,Java虚拟机需要完成以下三件事:
- 通过一个类的全限定名来获取这个类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
对于 Class 文件,虚拟机没有指明要从哪里获取、怎样获取。除了直接从编译好的 .class 文件中读取,还有以下几种方式:
- 从ZIP压缩包中获取(JAR、EAR、WAR)
- 从网络中获取(Web Applet)
- 运行时计算生成(动态代理技术)
- 由其它文件生成(JSP)
- 从数据库读取
- ....
验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
Java语言本身是相对安全的,使用纯粹的Java代码无法做到像访问数组边界以外的数据、将一个对象转型成它未实现的类型、跳转到不存在的代码行之类的事情。但由于Class文件并不一定由Java源码编译而来,上诉Java代码无法做到的事在字节码层面可以实现。Java虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了错误或有恶意企图的字节码导致整个系统受到攻击甚至崩溃。
验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。
1.文件格式验证
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理,验证点如下:
- 是否以魔数 0XCAFEBABE 开头。
- 主次版本号是否在当前虚拟机处理范围内。
- 常量池是否有不被支持的常量类型。
- 指向常量的索引值是否指向了不存在的常量。
- CONSTANT_Utf8_info 型的常量是否有不符合 UTF8 编码的数据。
- ......
文件格式验证这一阶段是基于该类的二进制字节流进行的,主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java 类型信息的要求。除了这一阶段之外,其余三个验证阶段都是基于方法区的存储结构上进行的,不会再直接读取、操作字节流了。
2.元数据验证
对字节码描述的信息进行语义分析,以保证其描述信息符合《Java语言规范》,这个阶段可能包括的验证点如下:
- 这个类是否有父类
- 这个类的父类是否继承了不被允许继承的类(被final修饰的类)
- 如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法
- ...
3.字节码验证
主要目的是通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段就要对类的方法体进行校验分析,保证被校验类的方法在运行是不会做出危害虚拟机安全的行为,例如:
- 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似于“在操作栈放置了一个int类型的数据,使用时却按long类型来加载本地变量表中”这样的情况
- 保证任何跳转指令都不会跳转到方法体以外的字节码指令上
- 保证方法体中的类型转换始终是有效的,例如可以把一个子类对象赋值给父类数据类型,但是把父类对象赋值给子类数据类型,甚至把对象赋值给它毫不相干的数据类型这是危险和不合法的
- ...
4.符号引用验证
符号引用验证发生在类加载过程中的解析阶段,具体点说是 JVM 将符号引用转化为直接引用的时候。
需要校验下列内容:
- 符号引用中通过字符串描述的全限定名是否能找到对应的类
- 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段
- 符号引用中的类、字段、方法的可访问性是否可被当前类访问
- ...
符号引用验证的主要目的是确保解析阶段能正常执行,如果无法通过符号引用验证,JVM 会抛出异常,比如: - java.lang.IllegalAccessError:当类试图访问或修改它没有权限访问的字段,或调用它没有权限访问的方法时,抛出该异常。
- java.lang.NoSuchFieldError:当类试图访问或修改一个指定的对象字段,而该对象不再包含该字段时,抛出该异常。
- java.lang.NoSuchMethodError:当类试图访问一个指定的方法,而该方法不存在时,抛出该异常。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
- 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
- 这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
- 如果类字段的字段属性表中存在 ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。
标签:Java,字节,验证,虚拟机,阶段,机制,加载 From: https://www.cnblogs.com/xiaoovo/p/17352231.html