java虚拟机中类加载的全过程:
- 加载
- 验证
- 准备
- 解析
- 初始化
“加载(Loading)”阶段是整个“类加载(Class Loading)”过程中的一个阶段,在加载阶段,java虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
“通过一个类的全限定名来获取定义此类的二进制字节流”这条规则并没有指明二进制字节流必须得从某个Class文件中获取,确切地说是根本没有指明要从哪里获取、如何获取。仅仅这一点,开发人员在这个舞台上玩出了各种花样,许多举足轻重的java技术都建立在这一基础之上。例如:
- 从ZIP压缩包中读取,这很常见,最终成为日后JAR、EAR、WAR格式的基础。
- 从网络中获取,这种场景最典型的应用就是Web Applet。
- 运行时计算生成,这种场景使用得最多得就是动态代理技术、在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass()来为特定接口生成形式为“*$Proxy”的代理类的二进制字节流。
- 由其他文件生成,典型场景是JSP应用,由JSP文件生成对应的Class文件。
- 从数据库中读取,这种场景相对少些,例如有些中间件服务器(如SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
- 可以从加密文件中获取,这是典型的防Class文件被反编译的保护措施,通过加载时解密Class文件来保障程序运行逻辑不被窥探。
相对于类加载过程中的其他阶段,非数组类型的加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的阶段。加载阶段既可以使用java虚拟机里内置的启动类加载器来完成,也可以由用户自定义的类加载器去完成,开发人员通过定义自己的类加载器去控制字节流的获取方式(重写一个类加载器的findClass()或loadClass()方法),实现根据自己的想法来赋予应用程序获取运行代码的动态性。
对于数组类而言,情况有所不同。数组类本身不通过类加载器创建,它是由java虚拟机直接在内存中动态构造出来的。 但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型(Element Type,指的是数组去掉所有维度的类型)最终还是要靠类加载器来完成加载,一个数组类(简称为C)创建过程遵循以下规则:
- 如果数组的组件类型(Component Type,指的是数组去掉一个维度的类型)是引用类型,那就递归采用本节中定义的加载过程去加载这个组件类型,数组C将被标识在加载该组件类型的类加载器的类名称空间上(一个类型必须与类加载器一起确定唯一性)。
- 如果数组的组件类型不是引用类型(例如int[]数组的组件类型为int),java虚拟机将会把数组C标记为与启动类加载器关联。
- 数组类的可访问性与它的组件类型的可访问性一致,如果组件类型不是引用类型,它的数组类的可访问性将默认为public,可被所有的类和接口访问到。
加载阶段结束后,java虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中了,方法区中的数据存储格式完全由虚拟机实现自行定义,《java虚拟机规范》未规定此区域的具体数据结构。类型数据妥善安置在方法区之后,会在java堆内存中实例化一个java.lang.Class类的对象,这个对象将作为程序访问方法区中类型数据的外部接口。
加载阶段与连接阶段的部分动作(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的一部分,这两个阶段的开始时间仍然保持着固定的先后顺序。
验证验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《java虚拟机规范》的全部约束要求,保证这些信息被当作代码不会危害虚拟机的自身安全。
Class文件并不一定只能由java源码编译而来,所以如果java虚拟机不检查输入的字节流,对其完全信任的话,很可能因为载入了有错误或有恶意企图的字节码流而导致整个系统收到攻击甚至崩溃,所以验证字节码是java虚拟机保护自身的一项必要措施。
从代码量和耗费的执行性能的角度上讲,验证阶段的工作量在虚拟机的类加载过程中占了相当大的比重。
验证阶段大致会完成下面四个阶段的检查动作:
- 文件格式验证
- 元数据验证
- 字节码验证
- 符合引用验证
这一阶段主要是验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,主要包括以下验证点:
- 是否以魔数0xCAFEBABE开头。
- 主、次版本号是否在当前java虚拟机接受范围之内。
- 常量池的常量中是否有不被支持的常量类型(检查常量tag标志)。
- 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
- CONSTANT_Utf8_info型的常量中是否有不符合UTF-8编码的数据。
- Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
- …
这阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证之后,这段字节流才被允许进入java虚拟机内存的方法区进行存储,所以后面的三个验证阶段全部是基于方法区的存储结构上进行的,不会再直接读取、操作字节流了。



