本篇主要涉及四种方法调用指令:invokestatic、invokespecial、invokevirtual以及invokeinterface。invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一调用版本,在类加载的时候就把符号引用解析为该方法的直接引用,剩下两种需要在运行时再确定。此外,final方法是非虚方法。
解析方法的符号引用
如无特别强调,下文中提到的参数个数表示参数所占Slot_实例数。
节约篇幅这里只列举解析非接口方法符号引用的例子(接口方法类似),首先放出代码:
|
|
解析的步骤:
- 解析方法出所属的类,判断是否是接口;
- 在继承中查找方法;
- 判断方法是否查找到;
- 判断当前类是否对解析的方法有访问权限;
由于Java 8之后接口可以拥有默认方法,所以在继承中查找方法未遂将继续在接口中查找。具体查找方式是通过比较简单名和描述符来实现的:
|
|
方法参数计数
一个简单的例子:
|
|
将其编译后用javap反编译字节码:
|
|
add02方法的描述符是(IILjava/lang/String;LClassFileDemo;)Ljava/lang/String
;
,下面给出本人计算传入参数个数的思路:
- 用正则表达式计算出括号内的字符串;
- 从上一步计算出的字符串中以非贪婪的方式解析出
L
开头;
结尾的字符串,求出此类字符串出现的次数r; - 从第一步计算出的字符串中剔除第二步算出的字符串部分,计算非
D
和J
的个数m以及D
和J
的个数n; - 参数个数即为r + m + 2 * n(如果方法不是静态还需要在此基础上加1);
用代码实现即:
|
|
操作数栈到局部变量表的映射逻辑
在main方法中观察add02()被调用前后的字节码指令,可知将传入被调用方法的参数首先会被压入当前帧的操作数栈,而在Java中,一个方法如果是静态
的,那么参数会从方法局部变量表的0号位置开始依次保存。所以当前帧的操作数栈中的元素和被调用方法的参数的对应关系如下:
根据对应关系给出核心代码:
|
|
总结
本篇讲解方法符号引用的解析,方法参数的计数和参数的映射,下篇将完成四种指令的实现。