虚拟机类加载机制

三、类加载的过程

1) 加载

加载是类加载过程的一个阶段, 这两个名称容易混淆, 在加载阶段, 虚拟机需要完成以下3件事件:

1 通过一个类的限定名来获取定义此类的二进制字节流.

2 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

3 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口.

虚拟机规范的这3点要求其实并不算具体, 因此虚拟机实现与具体应用的灵活程序都是相当大的.例如, "通过一个类的全限定名来获取定义此类的二进制字节流" 这条, 它没有指明二进制字节流要从一个class文件中获取, 准确地说是根本没有指明要从哪里获取, 怎样获取. 虚拟机设计团队在加载阶段搭建了一个相当开放的,广阔的舞台, java发展历程中, 充满创造力的开发人员则在这个舞台上玩出了各种花样, 许多举足轻重的java技术都建立在这一基础之个, 例如:

从zip包中读取,这很常见, 最终成为日后jar, ear, war格式的基础

从网络中获取,这种场景最典型的应用就是Applet

运行进计算生成,这种场景使用得最多的就是动态代理技术, 在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass来为特定接口生成形式为"*$Proxy"的代理类的二进制字节流.

其他文件生成,典型场景是jsp应用, 即由jsp文件生成对应的class类

从数据库中读取,这种场景相对较少些, 例如有些中间件服务器(如SAP Netweaver) 可以选择把程序安装到数据库中来完成程序代码在集群间的分发.

相对于类加载过程的其他阶段, 一个非数组类的加载阶段是开发人员可控性最强的, 因为加载阶段既可以使用系统提供的引导类加载器来完成,也可以由用户自定义的类加载器去完成, 开发人员可以通过定义自己的类加载器去控制字节流的获取方式(即重写一个类加载器的loadClass()方法).

对于数组类而言, 情况就有所不同,数组类本身不通过类加载器创建, 它是由java虚拟机直接创建的.但数组类与类加载器仍然有很密切的关系, 因为数组类的元素类型最终是要靠类加载器去创建,一个数组类创建过程遵循以下规则:

如果数组的组件类型是引用类型, 就递归采用前面所描述的过程加载这个组件类型, 数组C将在加载该组件类型的类加载器的类名称空间上被标识.

如果数组的组件类型不是引用类型,java虚拟机将会把数组C标记为与引导类加载器关联.

数组类的可见性与它的组件类型的可见性一致, 如果组件类型不是引用类型, 那数组类的可见性将默认为public.

加载阶段完成后, 虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义, 虚拟机规范未规定此区域的具体数据结构. 然后在内存中实例化一个java.lang.Class类的对象(并没有明确规定是在java堆中,对于hotspot虚拟机而言, class对象比较特殊,它虽然是对象,但是存放在方法区里面) 这个对象将作为程序访问方法区中的这些类型数据的外部接口.

加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的, 加载阶段尚未完成, 连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的内容, 这两个阶段的开始时间仍然保持着固定的先后顺序.