Resolution in《The Java Virtual Machine Specification Java SE 7 Edition》
Resolution:简单地说就是将符号引用转化为直接引用。
在JVM指令集中,anewarray, checkcast, getfield,getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic,invokevirtual, ldc, ldc_w, multianewarray, new, putfield, and putstatic指令都涉及符号引用(符号引用是存储在method area的rumtime constant pool中),执行这些指令都会触发符号引用的解析。例如,调用实例方法指令:invokevirtual,其执行格式为:invokevirtual #index,其中#index为runtime constant pool中constant数组的索引,在该索引位置是一个关于方法的符号引用,执行这条JVM指令就需要先解析该符号引用,将其解析为一个调用点说明符(call site specifier)。其中,对invokedynamic指令,相同的符号引用只需要解析一次即可(因为解析invokedynamic指令相关的符号引用的结果是一个指向调用点对象(call site object)的引用,调用点对象会与该invokedynamic指令绑定),而以上其他指令,相同的符号引用在每次执行时都需要进行解析。在解析过程中,如果一个符号引用已经解析成功,后续如果尝试该符号引用解析,都直接成功返回;如果解析失败,会在程序触发解析(直接或间接)的地方抛出IncompatibleClassChangeError或其子类的异常;如果解析过程因为LinkageError或其子类的异常而失败,则后续如果尝试该符号引用的解析,都直接返回第一次失败的错误。
一. 类解析(Class and Interface Resolution)(如果无特殊说明,本文中的“类”表示类和接口,下同)
假设类D中引用了类C符号引用,该符号引用未解析,类C的全限定名称为N,其解析过程如下:
1. JVM用类D的“the defining loader”去创建(包括加载、连接、初始化)N,创建过程中出现的任何异常都可视为类C符号引用的解析异常;
2. 如果C是一个数组类型,且其元素类型是一个符号引用,那用步骤1对其元素符号引用进行解析;
3. 检查类D是否可以访问到类C,如果不能,则抛出IllegalAccessError异常。这种情况是可能出现的,例如,类C最初声明为public类型,但是在类D编译过后声明改为非public类型,这个时候类D是访问不到类C的。
如果步骤1、2都成功,但3失败,类C仍然是有效可用的,但是本次关于类C符号引用解析失败,D也不能访问 到类C。
二. 属性解析(Field Resolution)
假设类D中引用了类C的属性符号引用F,F未解析,其解析过程如下:
1. 若类C符号引用未解析,首先按照“类解析”的步骤解析类C符号引用,解析过程中关于类C符号引用的异常可视为解析类C属性符号引用的异常;
2. 若类C中有按照F指定的属性名称及描述符申明属性,则解析成功,类C中所声明的属性即为解析结果;否则继续;
3. 若类C有直接superinterfaces,则按照步骤2的方式在superinterfaces中递归查找,如果查找到,则解析成功;否则继续;
4. 若类C有直接superclass,则按照步骤2的方式在superclass中递归查找,如果查找到,则解析成功;否则F的解析失败,解析结束。
若未查找到F,则抛出NoSuchFieldError异常;若查找F成功,但是类D访问不到F,则属性解析抛出IllegalAccessError异常。
“属性解析”须满足如下加载约束条件:
假设F在中声明,
,F的类型为
,若
非数组类型,T为
,否则T为
的元素类型,则满足加载约束:
。
三. 方法解析(Method Resolution)
假设类D中引用了类C的方法符号引用M,该符号引用未解析,其解析过程如下:
1. 若类C符号引用未解析,首先按照“类解析”的步骤解析类C符号引用,解析过程中关于类C符号引用的异常可视为解析M的异常;
2. 检查C是否为class,若C为interface,则抛出IncompatibleClassChangeError;
3. 若类C中有按照M指定的名称申明方法,且该声明是一个签名多态的方法,则解析成功,该方法中描述符用到的所有类符号引用都成功解析;否则继续;
4. 若类C中有按照M指定的名称及描述符声明方法,则解析成功;否则继续;
5. 若类C有直接superclass,则按照步骤3—4的方式在superclass中递归查找,如果查找到,则解析成功;否则继续;
6. 若类C有直接superinterfaces,则按照步骤4的方式在superinterfaces中递归查找,如果查找到,则解析成功;否则M的解析失败,解析结束;
若未查找到M,则抛出NoSuchMethodError异常;若查找M成功,且查找到的方法是abstract,但是C不是abstract,则抛出AbstractMethodError;若查找M成功,但是类D访问不到M,则抛出IllegalAccessError异常。
“方法解析”须满足如下加载约束条件:
假设M查找到的方法在中声明,
,M的返回类型为
,参数类型为
,如果
不是数组类型,假设
为
,否则
为
的元素类型,对于i=1,...,n,如果
不是数组类型,假设
为
,否则
为
的元素类型,则对于i=0,...,n满足:
。
四. 接口方法解析(Interface Method Resolution)
假设类D中引用了interface C的方法符号引用m,该符号引用未解析,其解析过程如下:
1. 若m中interface C的符号引用未解析,则首先按照“类解析”的步骤解析interface C符号引用,解析过程中关于interface C符号引用的异常可视为解析m的异常;
2. 检查C是否为interface,若C不为interface,则抛出IncompatibleClassChangeError;
3. 若C中有按照m指定的名称及描述符声明方法,则解析成功;否则继续;
4. 若C有直接superinterfaces,则按照步骤3的方式在superinterfaces中递归查找,如果查找到,则解析成功;否则继续;
5. 按照步骤3的方式在java.lang.Object中查找,如果查找到,则解析成功;否则m的解析失败,解析结束;
若未查找到m,则抛出NoSuchMethodError异常。
“接口方法解析”须满足如下加载约束条件:
假设m查找到的方法在中声明,
,m的返回类型为
,参数类型为
,如果
不是数组类型,假设
为
,否则
为
的元素类型,对于i=1,...,n,如果
不是数组类型,假设
为
,否则
为
的元素类型,则对于i=0,...,n满足:
。
五. 方法类型和方法句柄解析(Method Type and Method Handle Resolution)
一个方法的类型用该方法的描述符来描述。
方法类型解析:一个方法类型的符号引用解析,就是解析其方法描述符用到的所有类符号引用,其解析结果是一个指向java.lang.invoke.MethodType实例的引用。解析过程中关于类符号引用的异常可视为解析方法类型的异常。
方法句柄的解析:JVM解析的每一种方法句柄在二进制指令序列中都可以用所谓的“字节码行为”表示:
假设MH为待解析的方法句柄符号引用,R为MH中属性或方法符号引用,C为R中类符号引用,f或m为R中属性或方法的名称符号引用,T、(方法类型使用)分别为R中属性或方法的返回值符号引用、参数符号引用,那么解析MH就是将C、f、m、T、
这些符号引用都解析出来。解析过程中关于类符号引用、属性符号引用、方法符号引用、接口方法符号引用的解析异常都可视为方法句柄的解析异常。这里需要对R、C、f、m、T、
的获取有一个了解,先看一下.class文件中常量池的各种常量结构关系图:
在“ClassFile”图中,ClassFile是包括常量池的,以数组的形式体现(当然这里说的“数组”不是java语法中的数组,因为该“数组”内的元素类型和长度都不相同),“数组”元素类型就是上图中所体现的这些常量结构。例如,MH的信息从体现为红色框的CONSTANT_MethodHandle_info结构形式的常量中获取,其中reference_kind表示方法句柄的字节码类型,reference_index为“数组”的索引,在该索引位置上为CONSTANT_Fieldref、CONSTANT_Methodref或CONSTANT_InterfaceMethodref类型的常量,当然上图中只给出了CONSTANT_Fieldref,其实CONSTANT_Methodref、CONSTANT_InterfaceMethodref类型结构和CONSTANT_Fieldref一样的,只是name_and_type_index字段意思不一样,其在CONSTANT_Fieldref中为关于属性名称和描述符的CONSTANT_NameAndType_info类型常量的索引,而在CONSTANT_Methodref、CONSTANT_InterfaceMethodref中为关于方法名称和描述符的CONSTANT_NameAndType_info类型常量的索引。所以R为从CONSTANT_Fieldref、CONSTANT_Methodref或CONSTANT_InterfaceMethodref类型常量中所获取的属性或方法符号引用,那C就是从class_index索引所体现的CONSTANT_Class类型常量中获取的类符号引用,f、m、T、符号引用的获取依此类推。MH的解析结果就是一个指向java.lang.invoke.MethodHandle实例的引用。
若 MH解析成功,那MH中的方法类型符号引用也就解析完成了,下图是方法句柄类型中所包含的方法描述符:
六. 调用点说明符解析(Call Site Specifier Resolution)
假设调用点说明符符号引用为CS,该符号引用未解析,则其解析过程如下:
1. 解析CS中的方法句柄符号引用,其解析结果为一个指向java.lang.invoke.MethodHandle实例的引用,该方法句柄表示一个对启动方法(bootstrap method)的动态调用点;
2. 解析CS中的方法描述符,其解析结果为一个指向java.lang.invoke.MethodType实例的引用;
3. 解析CS中的静态参数符号引用,静态参数可为java.lang.Class类型、java.lang.invoke.MethodHandle类型、java.lang.invoke.MethodType类型、java.lang.String类型,其解析结果为指向这些类型实例的引用。
附上官方《The Java® Virtual Machine Specification Java SE 7 Edition》链接:https://docs.oracle.com/javase/specs/jvms/se7/html/index.html
链接来自:https://blog.csdn.net/Architect0719/article/details/50499736