java虚拟机规范———加载,链接,初始化
/**
* java虚拟机规范———加载,链接,初始化
* java虚拟机动态地加载,链接,初始化类和接口。
* @加载时根据特定名称查找类或接口类型的二进制表示,并由此二进制表示来创建类或接口的过程。
* @链接是为了让类或接口可以被java虚拟机执行,而将类或接口并入虚拟机运行时状态的过程。
* @类和接口的初始化是指执行类或接口初始化方法<cinit>方法
*/
运行时常量池
/**
* @运行时常量池
* java虚拟机为每个类型都维护一个常量池。该常量池是java虚拟机运行时数据结构
* 当类和接口创建时,它的二进制表示中的常量池表被用来构造运行时常量池。
* 运行时常量池中的所有引用最初都是符号引用
* 从类和接口的二进制表示中得出符号
* @某个类或接口的符号引用来自于类或接口二进制表示中的CONSTANT_Class_info结构
* 对于非数组的类或接口,此名称是类或接口的二进制名称
* 对于一个n维的数组类,名称会以n个ASCII“[”表示
*
* @类或接口的某个字段的符号引用来自于类或接口二进制表示中的CONSTANT_Fieldref_info 结构
*
* @类中某个方法的符号引用来自于类或接口二进制表示中的CONSTANT_Methodref_info 结构
*
* @接口中某个方法的符号引用来自于类或接口二进制表示中的CONSTANT_InterfaceMethodref_info结构
*
* @方法句柄的符号类型来自于类或接口二进制表示中的CONSTANT_MethodHandle_info结构中
*
* @方法类型的符号引用来自于类或接口二进制表示中的CONSTANT_MethodType_info结构
*
* @调用点限定符的符号来自于类或接口二进制表示的CONSTANT_InvokeDynamic_info结构中
*
* java语言规定,相同的字符串常量必须指向同一个String类实例
* 如果某String实例所包含的Unicode码点序列与CONSTANT_String_info结构所给出的序列相同。此次常量获取指向同一个String实例
*
* 否则会创建一个新的String实例,其中包含由CONSTANT_String_info 结构所给出Unicode码点序列;
*/
虚拟机启动
/**
* @虚拟机启动
* @java虚拟机的启动是通过引导类加载器创建一个初始类来完成的。这个类是由虚拟机的具体实现指定的。
* @Java虚拟机链接这个初始类,初始化并调用它的public方法void main(String[])
* 之后的整个执行过程都是由对此方法的调用开始的。
* @执行main方法中的java虚拟机指令可能会导致java虚拟机链接(并于其后创建)另外一些类或接口
* 也可能会令虚拟机调用另外的方法
*
* 在某种java虚拟机的实现,初始类可能会作为命令参数提供给虚拟机。
*/
创建和加载
/**
* @创建和加载
* @如果要创建标记为N的类或接口C,就需要先在java虚拟机方法区上为C创建与虚拟机实现相匹配的内部表示。
* C接口的创建是由另外的类或接口D所触发,它通过自己的运行时常量池引用了C.
* C接口的创建也可能是D调用SE平台类库中触发的,比如反射调用。
*
* @如果C不是数组类,那么它可以通过类加载C的二进制表示来创建。数组类没有外部的二进制表示,他们是由java虚拟机创建的,
* 而不是通过类加载器加载的。
*
* @java虚拟机支持两种类加载器
* java虚拟机提供的引导类加载器和用户自定义的类加载器
* 用户自定义的类加载器应该是抽象类ClassLoader的某个子类的实例。
*
* @当一个类加载器把加载请求委托给其他的类加载器后,发出这个加载请求的加载器与最终完成加载并定义类的加载器不需要是同一个加载器。
* 如果L 创建了C,那么它可能是通过直接定义的方式,或是委托给其它加载器的方式来创建的C
* L导致了C的加载
* 在java虚拟机的运行时,类或接口不仅仅是由它的名称来确定,而是由一个值对:二进制名称和它的定义类加载器共同确定的
* 每个这样的类或接口都只属于一个运行时包结构,类或接口的运行时包结构由包名及类或接口的定义类加载器决定。
*
* @java虚拟机通过下面三个过程之一来创建标记为N的类或接口C
* @如果N表示一个非数组的类或接口,那么可以用下面的两个方法之一来加载并创建C
* @如果D是由引导类加载器所定义的,那么用引导类加载器初始加载C
* @如果D是由用户自定义类加载器锁定义的,那么就用这个用户自定义类加载器来初始加载C
* @如果N表示一个数组类,那该数组类是由java虚拟机而不是类加载器创建的,在创建数组类C的过程中,也会用到D的定义类加载器
*
* @如果在类加载过程中产生错误,那么某个LinkageError的子类的实例必须被抛出。
* 抛出的位置应该是用到了当前正在加载进来的类或接口的那个地方。
*
* @如果Java虚拟机试图在验证或解析但还没有初始化时加载了C类,而用于加载C的初始化类加载器抛出了ClassNotFoundException实例
* 那么java虚拟机必须抛出NoClassDefFoundError异常,cause字段中就保存了那个CNFE异常实例。
*
* @一个功能良好的类加载器保证下面三个属性
* 给定相同的名称,加载器总是鞥能够返回相同的Class对象
* 如果类加载器L1将加载类C的请求委托给另外的类加载器L2,那么对于满足下列条件之一的任意类型T来说,
* L1和L2都应该返回相同的Class对象,
* T是C的直接超类或直接超接口,T是C中某个字段的类型,T是C中某个方法或构造器的形式参数类型
* T是C中某个方法的返回值类型
* 如果某个用户自定义的类加载器预先加载了某各类或接口的二进制表示,或批量加载了一组相关的类,并在加载时出现了错误
* 那它必须在程序的某个点反应出加载时的错误,
* 而这个点,一定要和不使用预先加载或批量加载时出现错误的那个点相同。
*
*
* @使用引导类加载器加载类型
* 使用引导类加载器加载并创建标记为N的非数组类或接口C
* 首先 java虚拟机检查引导类加载器是否已标注成用N来表示的类或接口的初始化器。
* 如果是这个类或接口就是C,并且不需要再创建类。
* 否则 java虚拟机将参数N传递给引导类加载器的特定方法,以平台相关的方式搜索C的描述符。
* 类或文件通常会表示为树型文件系统中的某个文件,类或接口的名称就蕴含在此文件的路径中。
* @搜索过程过程中并不一定可以找到C的描述,也不保证找到的那个描述符就是C的描述符。
* 如果没有找到与C相关的描述,那么加载过程要抛出CNFE异常
*
* @使用用户自定义类加载器来加载类型
* 自定义加载器L来加载并创建标记为N的非数组类或接口C
* @首先
* java虚拟机检查L是否已经标注为N所表示的类或接口的初始加载器,如果是,则类或接口就是C,不在继续创建类
*
* @否则
* java虚拟机会调用L的loadClass(N)方法,每次调用的返回值就是创建好的类或接口C
* java虚拟机会记录下L是C的初始加载器
*
* 当通过类或接口C的名称N去调用类加载器L的loadClass方法,L必须执行下面两种操作之一加载C
* 1、类加载器L可以通过创建一个如ClassFile的字节数组来表示C
* 然后必须调用ClassLoader的defineClass方法
* 调用defineClass方法会让虚拟机使用描述算法通过L由字节数组得到标记为N的类或接口
*
* 2、类加载器L可能把对C的加载委托给其他的类加载器L1
* 这是通过直接或间接传递参数N来调用L1的方法(一般loadClass方法)而完成的,这次调用产生了C
*
* 无论哪种方式导致无法加载类都会抛出CNFE异常。
*/