This article is authored by HeapDump performance community chief lecturer Kumo (Ma Zhi) to collate and publish
Part 29-Call the main() method of the Java main class
I have written many articles describing the assembly code execution logic corresponding to bytecode instructions, and some assembly code logic corresponding to bytecode instructions have not been introduced. These instructions include method call instructions, synchronization instructions, and exception throw instructions. These instructions The implementation logic of the assembly code is more complicated, so when we introduce the knowledge points of method calling, synchronization and exception handling later, we will introduce them in detail through a large article!
In the first chapter, I roughly introduced the calling process of the main class method main() in Java. This article introduces a little bit more in detail, and the general calling process is shown in the following figure.
The light red function is executed by the main thread, and the other light green part is executed by another thread. The light green thread will eventually be responsible for executing the main() method in the Java main class. Call the LoadMainClass() function in the JavaMain() function to load the Java main class. Then there is the following call in the JavaMain() function:
源代码位置:openjdk/jdk/src/share/bin/java.c
mainID = (*env)->GetStaticMethodID(
env,
mainClass,
"main",
"([Ljava/lang/String;)V");
env is of type JNIEnv*. Call the GetStaticMethodID() function defined in the JNIEnv type to obtain the method unique ID of the main() method in the Java main class. Calling the GetStaticMethodID() function is calling the jni_GetStaticMethodID() function. The implementation of this function is as follows:
源代码位置:openjdk/hotspot/src/share/vm/prims/jni.cpp
JNI_ENTRY(jmethodID, jni_GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig))
jmethodID ret = get_method_id(env, clazz, name, sig, true, thread);
return ret;
JNI_END
static jmethodID get_method_id(
JNIEnv *env,
jclass clazz,
const char *name_str,
const char *sig,
bool is_static,
TRAPS
){
const char *name_to_probe = (name_str == NULL)
? vmSymbols::object_initializer_name()->as_C_string()
: name_str;
TempNewSymbol name = SymbolTable::probe(name_to_probe, (int)strlen(name_to_probe));
TempNewSymbol signature = SymbolTable::probe(sig, (int)strlen(sig));
KlassHandle klass(THREAD,java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
// 保证java.lang.Class类已经初始化完成
klass()->initialize(CHECK_NULL);
Method* m;
if ( name == vmSymbols::object_initializer_name() || 查找的是<init>方法
name == vmSymbols::class_initializer_name() ) { 查找的是<clinit>方法
// 因为要查找的是构造函数,构造函数没有继承特性,所以当前类找不到时不向父类中继续查找
if (klass->oop_is_instance()) {
// find_method()函数不会向上查找
m = InstanceKlass::cast(klass())->find_method(name, signature);
} else {
m = NULL;
}
} else {
// lookup_method()函数会向上查找
m = klass->lookup_method(name, signature);
if (m == NULL && klass->oop_is_instance()) {
m = InstanceKlass::cast(klass())->lookup_method_in_ordered_interfaces(name, signature);
}
}
return m->jmethod_id();
}
Get the jmethod_id of the main() method in the Java class.
源代码位置:method.hpp
// Get this method's jmethodID -- allocate if it doesn't exist
jmethodID jmethod_id() {
methodHandle this_h(this);
return InstanceKlass::get_jmethod_id(method_holder(), this_h);
}
Call the InstanceKlass::get\_jmethod\_id() function to obtain the unique ID. The process of how to obtain or generate the ID will not be described in detail here. If you are interested, you can study it yourself.
There are the following calls in the JavaMain() function:
mainArgs = CreateApplicationArgs(env, argv, argc);
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
Call the main() method in the Java main class by calling the CallStaticVoidMethod() function. Control is transferred to the main() method in the Java main class. To call the CallStaticVoidMethod() function is to call the jni_CallStaticVoidMethod() function. The implementation of this function is as follows:
源代码位置:openjdk/hotspot/src/share/vm/prims/jni.cpp
JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...))
va_list args;
va_start(args, methodID);
JavaValue jvalue(T_VOID);
JNI_ArgumentPusherVaArg ap(methodID, args);
jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK);
va_end(args);
JNI_END
After the parameters passed to the Java method are passed in as C variable-length parameters, the JNI_ArgumentPusherVaArg instance ap is used to encapsulate them. The inheritance system of the JNI_ArgumentPusherVaArg class is as follows:
JNI_ArgumentPusherVaArg->JNI_ArgumentPusher->SignatureIterator
The implementation of the called jni\_invoke\_static() function is as follows:
// 通过jni的方式调用Java静态方法
static void jni_invoke_static(
JNIEnv *env,
JavaValue* result,
jobject receiver,
JNICallType call_type,
jmethodID method_id,
JNI_ArgumentPusher *args,
TRAPS
){
Method* m = Method::resolve_jmethod_id(method_id);
methodHandle method(THREAD, m);
ResourceMark rm(THREAD);
int number_of_parameters = method->size_of_parameters();
// 这里进一步将要传给Java的参数转换为JavaCallArguments对象传下去
JavaCallArguments java_args(number_of_parameters);
args->set_java_argument_object(&java_args);
// Fill out(填,填写) JavaCallArguments object
Fingerprinter fp = Fingerprinter(method);
uint64_t x = fp.fingerprint();
args->iterate(x);
// Initialize result type
BasicType bt = args->get_ret_type();
result->set_type(bt);
// Invoke the method. Result is returned as oop.
JavaCalls::call(result, method, &java_args, CHECK);
// Convert result
if (
result->get_type() == T_OBJECT ||
result->get_type() == T_ARRAY
) {
oop tmp = (oop) result->get_jobject();
jobject jobj = JNIHandles::make_local(env,tmp);
result->set_jobject(jobj);
}
}
Call the main() method of the Java main class through the JavaCalls::call() function. You should not be unfamiliar with the JavaCalls::call() function. How this function creates a Java stack frame and finds the Java method entry has been introduced in detail before, so I won’t introduce it here.
Chapter 30-A small example of explaining the execution of the main() method
After introducing the assembly code execution logic of some commonly used bytecode instructions, we basically saw the whole logic of a main() method from the beginning of the call, stack frame establishment, and bytecode execution, but the method exits the stack, the synchronization method, and Knowledge points such as exception throwing have not been introduced yet. We only give a simple example here to help you review what you have learned in so many previous articles.
The stack frame created for the Java method was introduced in detail in Chapter 7, as shown in the figure below.
The values saved in some registers after calling the generate\_fixed\_frame() function are as follows:
rbx:Method*
ecx:invocation counter
r13:bcp(byte code pointer)
rdx:ConstantPool* 常量池的地址
r14:本地变量表第1个参数的地址
Now let's take an example to explain the execution process completely. This example is as follows:
package com.classloading;
public class Test {
public static void main(String[] args) {
int i = 0;
i = i++;
}
}
The content of the bytecode file decompiled by the javap -verbose Test.class command is as follows:
Constant pool:
#1 = Methodref #3.#12 // java/lang/Object."<init>":()V
#2 = Class #13 // com/classloading/Test
#3 = Class #14 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 main
#9 = Utf8 ([Ljava/lang/String;)V
#10 = Utf8 SourceFile
#11 = Utf8 Test.java
#12 = NameAndType #4:#5 // "<init>":()V
#13 = Utf8 com/classloading/Test
#14 = Utf8 java/lang/Object
{
...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: return
}
The stack frame status corresponding to the above example is shown in the figure below.
Now we execute the bytecode in the main() method in an interpreted execution manner. Since it is called from the virtual machine, and the values saved in some registers after calling the generate\_fixed\_frame() function do not involve the stack top cache, so you need to enter from the vtos entry of the bytecode instruction of iconst\_0 , And then find the machine instruction fragment corresponding to the bytecode instruction of iconst\_0.
Now review the logic of bytecode dispatch. In the generate\_normal\_entry() function, the generate\_fixed\_frame() function will be called to generate the corresponding stack frame for the execution of the Java method, and then the dispatch_next() function will be called. The bytecode of the Java method is executed, and the assembly when the bytecode is first obtained is as follows:
// 在generate_fixed_frame()方法中已经让%r13存储了bcp
movzbl 0x0(%r13),%ebx // %ebx中存储的是字节码的操作码
// $0x7ffff73ba4a0这个地址指向的是对应state状态下的一维数组,长度为256
movabs $0x7ffff73ba4a0,%r10
// 注意%r10中存储的是常量,根据计算公式%r10+%rbx*8来获取指向存储入口地址的地址,
// 通过*(%r10+%rbx*8)获取到入口地址,然后跳转到入口地址执行
jmpq *(%r10,%rbx,8)
Note that the constant value of $0x7ffff73ba4a0 above has already indicated the first address of the one-dimensional array under the vtos cache state on the top of the stack. When the bytecode of the method is assigned for the first time, the Opcode corresponding to the bytecode can be retrieved through 0x0 (%r13), and the entry address of isconst_0 can be located using this Opcode.
%r10 points to a one-dimensional array corresponding to the top of the stack cache state state, the length is 256, and the stored value is Opcode, which is described in detail in Chapter 8. The schematic diagram is shown in the following figure.
Now look at the assembly code to be executed by isconst_0 whose entry is vtos and exit is itos, as follows:
...
// vtos入口
mov $0x1,%eax
...
// iconst_0对应的汇编代码
xor %eax,%eax
The assembly instruction is simple enough, and finally the value is stored in %eax, so the exit status of the stack top cache is itos.
The purple part of the picture above is the local variable table. Since the size of the local variable table is 2, I drew 2 squares to represent the slot.
Executing the next bytecode instruction istore_1 will also execute the logic related to bytecode dispatch. I need to be reminded here. In fact, when introducing the assembly corresponding to bytecode instructions, we only paid attention to the execution logic of the bytecode instruction itself. In fact, when generating machine instructions for each bytecode instruction, these bytes are generally used. Code instructions generate 3 parts of machine instruction fragments:
(1) Entrance execution logic corresponding to different stack top states;
(2) The logic to be executed by the bytecode instruction itself;
(3) The logic assigned to the next bytecode instruction.
For the bytecode instruction template definition, if the instructions in flags have disp, then these instructions themselves will contain dispatch logic, such as goto, ireturn, tableswitch, lookupswitch, jsr, etc. Since our instruction is isconst_0, we will generate dispatch logic for this bytecode instruction. The generated logic is as follows:
movzbl 0x1(%r13),%ebx // %ebx中存储的是字节码的操作码
movabs itos对应的一维数组的首地址,%r10
jmpq *(%r10,%rbx,8)
It should be noted that if you want to store the Opcode of istore\_1 in %ebx, %r13 needs to add the length of the isconst\_0 instruction, which is 1. Since the top cache of the exit stack after execution of iconst\_0 is itos, it is necessary to find the machine instruction fragments whose entry state is itos and Opcode is istore\_1. The instruction fragment is as follows:
mov %eax,-0x8(%r14)
The code stores the value %eax at the top of the stack in the location where the subscript index of the local variable table is 1. It is easy to locate the location of the local variable table through %r14, and the stack state after execution is as shown in the figure below.
When executing iconst\_0 and istore\_1, the whole process did not push 0 into the expression stack (the part below the beginning of sp/rsp in the above figure is the expression stack). In fact, if there is no optimization of the stack top cache, it should be 0 is pushed onto the top of the stack, and then popped from the top of the stack and stored in the local variable table, but with the top of the stack cache, there is no push operation, and there is also a pop operation, so the execution efficiency of the program can be greatly improved.
The return instruction has more logic to judge, mainly because some methods may have the synchronized keyword, so the lock-related information will be stored in the method stack, and when the return returns, the stack must be released to release the lock. But we only look at the part of the code to be run for this example, as follows:
// 将JavaThread::do_not_unlock_if_synchronized属性存储到%dl中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置JavaThread::do_not_unlock_if_synchronized属性值为false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)
// 将Method*加载到%rbx中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 将Method::_access_flags加载到%ecx中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 检查Method::flags是否包含JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果方法不是同步方法,跳转到----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970
The main() method is an asynchronous method, so it jumps to the unlocked location and executes some lock release logic in the logic executed at the unlocked location. This is not important for our example. Let’s look directly at the operation of exiting the stack. as follows:
// 将-0x8(%rbp)处保存的old stack pointer(saved rsp)取出来放到%rbx中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
// 移除栈帧
// leave指令相当于:
// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq
// 将返回地址弹出到%r13中
0x00007fffe101bacc: pop %r13
// 设置%rsp为调用者的栈顶值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13
This compilation is not difficult, so I won't continue to introduce it here. The stack state after unstacking is shown in the figure below.
This is completely back to the stack state before calling the Java method, and then how to exit the stack frame as above and end the method call is a matter of the C++ language.
Chapter 31-invokevirtual of method call instruction
The template of invokevirtual bytecode instruction is defined as follows:
def(Bytecodes::_invokevirtual , ubcp|disp|clvm|____, vtos, vtos, invokevirtual , f2_byte );
The generation function is invokevirtual, and the passed parameter is f2_byte, which is 2. If it is 2, ConstantPoolCacheEntry::indices takes the b2 part of [b2,b1,original constant pool index]. The implementation of the called TemplateTable::invokevirtual() function is as follows:
void TemplateTable::invokevirtual(int byte_no) {
prepare_invoke(byte_no,
rbx, // method or vtable index
noreg, // unused itable index
rcx, // recv
rdx); // flags
// rbx: index
// rcx: receiver
// rdx: flags
invokevirtual_helper(rbx, rcx, rdx);
}
First call the prepare\_invoke() function, and then call the invokevirtual\_helper() function to generate the assembly code corresponding to the invokevirtual bytecode instruction (actually, it generates the machine instruction, and then decompiles the corresponding assembly code. We will directly express it as Assembly code, readers should know).
1, prepare_invoke() function
There are many assembly codes generated by calling TemplateTable::prepare_invoke() function, so we will examine it in three parts.
part 1:
0x00007fffe1021f90: mov %r13,-0x38(%rbp) // 将bcp保存到栈中
// invokevirtual x中取出x,也就是常量池索引存储到%edx,
// 其实这里已经是ConstantPoolCacheEntry的index,因为在类的连接
// 阶段会对方法中特定的一些字节码指令进行重写
0x00007fffe1021f94: movzwl 0x1(%r13),%edx
// 将ConstantPoolCache的首地址存储到%rcx
0x00007fffe1021f99: mov -0x28(%rbp),%rcx
// 左移2位,因为%edx中存储的是ConstantPoolCacheEntry索引,左移2位是因为
// ConstantPoolCacheEntry占用4个字
0x00007fffe1021f9d: shl $0x2,%edx
// 计算%rcx+%rdx*8+0x10,获取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices
// 因为ConstantPoolCache的大小为0x16字节,%rcx+0x10定位
// 到第一个ConstantPoolCacheEntry的位置
// %rdx*8算出来的是相对于第一个ConstantPoolCacheEntry的字节偏移
0x00007fffe1021fa0: mov 0x10(%rcx,%rdx,8),%ebx
// 获取ConstantPoolCacheEntry中indices[b2,b1,constant pool index]中的b2
0x00007fffe1021fa4: shr $0x18,%ebx
// 取出indices中含有的b2,即bytecode存储到%ebx中
0x00007fffe1021fa7: and $0xff,%ebx
// 查看182的bytecode是否已经连接
0x00007fffe1021fad: cmp $0xb6,%ebx
// 如果连接就进行跳转,跳转到resolved
0x00007fffe1021fb3: je 0x00007fffe1022052
Mainly check whether the bytecode has been connected. If there is no connection, it needs to be connected. If it has been connected, then jump to resolved to directly execute the method call operation.
part 2:
// 调用InterpreterRuntime::resolve_invoke()函数,因为指令还没有连接
// 将bytecode为182的指令移动到%ebx中
0x00007fffe1021fb9: mov $0xb6,%ebx
// 通过调用MacroAssembler::call_VM()函数来调用
// InterpreterRuntime::resolve_invoke(JavaThread* thread, Bytecodes::Code bytecode)函数
// 进行方法连接
0x00007fffe1021fbe: callq 0x00007fffe1021fc8
0x00007fffe1021fc3: jmpq 0x00007fffe1022046 // 跳转到----E----
// 准备第2个参数,也就是bytecode
0x00007fffe1021fc8: mov %rbx,%rsi
0x00007fffe1021fcb: lea 0x8(%rsp),%rax
0x00007fffe1021fd0: mov %r13,-0x38(%rbp)
0x00007fffe1021fd4: mov %r15,%rdi
0x00007fffe1021fd7: mov %rbp,0x200(%r15)
0x00007fffe1021fde: mov %rax,0x1f0(%r15)
0x00007fffe1021fe5: test $0xf,%esp
0x00007fffe1021feb: je 0x00007fffe1022003
0x00007fffe1021ff1: sub $0x8,%rsp
0x00007fffe1021ff5: callq 0x00007ffff66ac528
0x00007fffe1021ffa: add $0x8,%rsp
0x00007fffe1021ffe: jmpq 0x00007fffe1022008
0x00007fffe1022003: callq 0x00007ffff66ac528
0x00007fffe1022008: movabs $0x0,%r10
0x00007fffe1022012: mov %r10,0x1f0(%r15)
0x00007fffe1022019: movabs $0x0,%r10
0x00007fffe1022023: mov %r10,0x200(%r15)
0x00007fffe102202a: cmpq $0x0,0x8(%r15)
0x00007fffe1022032: je 0x00007fffe102203d
0x00007fffe1022038: jmpq 0x00007fffe1000420
0x00007fffe102203d: mov -0x38(%rbp),%r13
0x00007fffe1022041: mov -0x30(%rbp),%r14
0x00007fffe1022045: retq
// 结束MacroAssembler::call_VM()函数的调用
// **** E ****
// 将invokevirtual x中的x加载到%edx中,也就是ConstantPoolCacheEntry的索引
0x00007fffe1022046: movzwl 0x1(%r13),%edx
// 将ConstantPoolCache的首地址存储到%rcx中
0x00007fffe102204b: mov -0x28(%rbp),%rcx
// %edx中存储的是ConstantPoolCacheEntry index,转换为字偏移
0x00007fffe102204f: shl $0x2,%edx
The connection logic of the method is similar to the connection logic of the fields introduced earlier, both of which are to improve the corresponding ConstantPoolCacheEntry in ConstantPoolCache and add relevant information.
Call the InterpreterRuntime::resolve_invoke() function to connect the method. There are many implementations of this function, and we will introduce it in detail in the next article. After the connection is complete, the items in ConstantPoolCacheEntry are shown in the following figure.
So for invokevirtual, the method is distributed through the vtable. In the ConstantPoolCacheEntry, the \_f1 field is not used, and for the \_f2 field, if the non-final virtual method is called, the target method in the vtable is saved Index number. If it is a virtual final method, the _f2 field directly points to the Method instance of the target method.
Part 3:
// **** resolved ****
// resolved的定义点,到这里说明invokevirtual字节码已经连接
// 获取ConstantPoolCacheEntry::_f2,这个字段只对virtual有意义
// 在计算时,因为ConstantPoolCacheEntry在ConstantPoolCache之后保存,
// 所以ConstantPoolCache为0x10,而
// _f2还要偏移0x10,这样总偏移就是0x20
// ConstantPoolCacheEntry::_f2存储到%rbx
0x00007fffe1022052: mov 0x20(%rcx,%rdx,8),%rbx
// ConstantPoolCacheEntry::_flags存储到%edx
0x00007fffe1022057: mov 0x28(%rcx,%rdx,8),%edx
// 将flags移动到ecx中
0x00007fffe102205b: mov %edx,%ecx
// 从flags中取出参数大小
0x00007fffe102205d: and $0xff,%ecx
// 获取到recv,%rcx中保存的是参数大小,最终计算参数所需要的大小为%rsp+%rcx*8-0x8,
// flags中的参数大小对实例方法来说,已经包括了recv的大小
// 如调用实例方法的第一个参数是this(recv)
0x00007fffe1022063: mov -0x8(%rsp,%rcx,8),%rcx // recv保存到%rcx
// 将flags存储到r13中
0x00007fffe1022068: mov %edx,%r13d
// 从flags中获取return type,也就是从_flags的高4位保存的TosState
0x00007fffe102206b: shr $0x1c,%edx
// 将TemplateInterpreter::invoke_return_entry地址存储到%r10
0x00007fffe102206e: movabs $0x7ffff73b6380,%r10
// %rdx保存的是return type,计算返回地址
// 因为TemplateInterpreter::invoke_return_entry是数组,
// 所以要找到对应return type的入口地址
0x00007fffe1022078: mov (%r10,%rdx,8),%rdx
// 向栈中压入返回地址
0x00007fffe102207c: push %rdx
// 还原ConstantPoolCacheEntry::_flags
0x00007fffe102207d: mov %r13d,%edx
// 还原bcp
0x00007fffe1022080: mov -0x38(%rbp),%r13
TemplateInterpreter::invoke\_return\_entry saves the entry of a routine, which will be described in detail later.
After executing the above code, the relevant value has been stored in the relevant register. The relevant register status is as follows:
rbx: 存储的是ConstantPoolCacheEntry::_f2属性的值
rcx: 就是调用实例方法时的第一个参数this
rdx: 存储的是ConstantPoolCacheEntry::_flags属性的值
The state of the stack is shown in the figure below.
The return address of TemplateInterpreter::invoke\_return\_entry is pushed into the stack.
2, invokevirtual_helper() function
The code generated by calling the TemplateTable::invokevirtual_helper() function is as follows:
// flags存储到%eax
0x00007fffe1022084: mov %edx,%eax
// 测试调用的方法是否为final
0x00007fffe1022086: and $0x100000,%eax
// 如果不为final就直接跳转到----notFinal----
0x00007fffe102208c: je 0x00007fffe10220c0
// 通过(%rcx)来获取receiver的值,如果%rcx为空,则会引起OS异常
0x00007fffe1022092: cmp (%rcx),%rax
// 省略统计相关代码部分
// 设置调用者栈顶并保存
0x00007fffe10220b4: lea 0x8(%rsp),%r13
0x00007fffe10220b9: mov %r13,-0x10(%rbp)
// 跳转到Method::_from_interpretered_entry入口去执行
0x00007fffe10220bd: jmpq *0x58(%rbx)
For the final method, there is actually no dynamic dispatch, so there is no need to look up the target through the vtable. The stack during the call is shown in the figure below.
The following code is to find the method entry that needs to be called for dynamic dispatch through the vtable.
// **** notFinal ****
// invokevirtual指令调用的如果是非final方法,直接跳转到这里
// %rcx中存储的是receiver,用oop来表示。通过oop获取Klass
0x00007fffe10220c0: mov 0x8(%rcx),%eax
// 调用MacroAssembler::decode_klass__not_null()函数生成下面的一个汇编代码
0x00007fffe10220c3: shl $0x3,%rax // LogKlassAlignmentInBytes=0x03
// 省略统计相关代码部分
// %rax中存储的是recv_klass
// %rbx中存储的是vtable_index,
// 而0x1b8为InstanceKlass::vtable_start_offset()*wordSize+vtableEntry::method_offset_in_bytes(),
// 其实就是通过动态分派找到需要调用的Method*并存储到%rbx中
0x00007fffe1022169: mov 0x1b8(%rax,%rbx,8),%rbx
// 设置调用者的栈顶地址并保存
0x00007fffe1022171: lea 0x8(%rsp),%r13
0x00007fffe1022176: mov %r13,-0x10(%rbp)
// 跳转到Method::_from_interpreted_entry处执行
0x00007fffe102217a: jmpq *0x58(%rbx)
To understand the above code, you need to know the vtable method dispatch and the layout of the vtable in InstanceKlass. This is described in detail in the book "In-depth analysis of the Java virtual machine: source code analysis and detailed examples", and will not be introduced here.
Jump to the execution of the routine saved in Method::\_from\_interpretered\_entry, that is, to interpret and execute the target method called by the invokevirtual bytecode instruction, about the routine saved in Method::\_from\_interpretered\_entry Logic has been introduced in detail in Chapter 6, Chapter 7, and Chapter 8, so I won't introduce it here.
The above assembly statement mov 0x1b8(%rax,%rbx,8),%rbx is generated by calling the lookup\_virtual\_method() function. This function loads vtable\_entry\_addr into %rbx, which is implemented as follows:
void MacroAssembler::lookup_virtual_method(Register recv_klass,
RegisterOrConstant vtable_index,
Register method_result) {
const int base = InstanceKlass::vtable_start_offset() * wordSize;
Address vtable_entry_addr(
recv_klass,
vtable_index,
Address::times_ptr,
base + vtableEntry::method_offset_in_bytes());
movptr(method_result, vtable_entry_addr);
}
The vtable\_index takes the value of ConstantPoolCacheEntry::\_f2.
Finally, some assembly codes generated above omit statistics-related execution logic. Statistics-related code is also very important here. It will assist compilation, so we will introduce these statistics-related logic later.
Part 32-Parsing interfacevirtual bytecode instructions
In the previous introduction of the invokevirtual instruction, if it is determined that the value of the \_f2 attribute of the \_indices field in ConstantPoolCacheEntry is empty, it is considered that the target method to be called is not connected, that is, the related information of the calling method is not saved in ConstantPoolCacheEntry, and InterpreterRuntime needs to be called. The ::resolve_invoke() function is used for method connection. There are many implementations of this function. Let's view it in several parts:
InterpreterRuntime::resolve_invoke() function part 1:
Handle receiver(thread, NULL);
if (bytecode == Bytecodes::_invokevirtual || bytecode == Bytecodes::_invokeinterface) {
ResourceMark rm(thread);
// 调用method()函数从当前的栈帧中获取到需要执行的方法
Method* m1 = method(thread);
methodHandle m (thread, m1);
// 调用bci()函数从当前的栈帧中获取需要执行的方法的字节码索引
int i1 = bci(thread);
Bytecode_invoke call(m, i1);
// 当前需要执行的方法的签名
Symbol* signature = call.signature();
frame fm = thread->last_frame();
oop x = fm.interpreter_callee_receiver(signature);
receiver = Handle(thread,x);
}
When the bytecode is dynamically allocated bytecode such as invokevirtual or invokeinterface, the above logic is executed. Get the value of the receiver variable. Then look at the implementation, as follows:
InterpreterRuntime::resolve_invoke() function part 2:
CallInfo info;
constantPoolHandle pool(thread, method(thread)->constants());
{
JvmtiHideSingleStepping jhss(thread);
int cpcacheindex = get_index_u2_cpcache(thread, bytecode);
LinkResolver::resolve_invoke(info, receiver, pool,cpcacheindex, bytecode, CHECK);
...
}
// 如果已经向ConstantPoolCacheEntry中更新了调用的相关信息则直接返回
if (already_resolved(thread))
return;
Obtain the operand of the bytecode instruction according to the bcp stored in the current stack. This operand is usually the constant pool cache entry index. Then call the LinkResolver::resolve\_invoke() function to connect the method. This function will indirectly call the LinkResolver::resolve\_invokevirtual() function, which is implemented as follows:
void LinkResolver::resolve_invokevirtual(
CallInfo& result,
Handle recv,
constantPoolHandle pool,
int index,
TRAPS
){
KlassHandle resolved_klass;
Symbol* method_name = NULL;
Symbol* method_signature = NULL;
KlassHandle current_klass;
resolve_pool(resolved_klass, method_name, method_signature, current_klass, pool, index, CHECK);
KlassHandle recvrKlass(THREAD, recv.is_null() ? (Klass*)NULL : recv->klass());
resolve_virtual_call(result, recv, recvrKlass, resolved_klass, method_name, method_signature, current_klass, true, true, CHECK);
}
The resolve\_pool() and resolve\_vritual_call() functions are called to connect the constant pool and method call instructions respectively. The related functions involved in the call are roughly as shown in the figure below.
The following describes the implementation of the resolve\_pool() and resolve\_virtual_call() functions and the related functions they call.
01 resolve_pool() function
The resolve_pool() function called will call some functions, as shown in the figure below.
Every time the LinkResolver::resolve\_pool() function is called, it will not necessarily be executed according to the above function call chain, but when the class has not been resolved, the SystemDictionary::resolve\_or_fail() function is usually called for resolution, and it will eventually get To the pointer to the Klass instance, the class is finally updated to the constant pool.
The implementation of resolve_pool() function is as follows:
void LinkResolver::resolve_pool(
KlassHandle& resolved_klass,
Symbol*& method_name,
Symbol*& method_signature,
KlassHandle& current_klass,
constantPoolHandle pool,
int index,
TRAPS
) {
resolve_klass(resolved_klass, pool, index, CHECK);
method_name = pool->name_ref_at(index);
method_signature = pool->signature_ref_at(index);
current_klass = KlassHandle(THREAD, pool->pool_holder());
}
The index is the index of the constant pool cache item. The resolved\_klass parameter indicates the class that needs to be parsed (analysis is to connect the relevant part in the class generation cycle, so we sometimes called it connection before, in fact, it means parsing), and current\_klass is currently owned The class of the constant pool, because the parameter is passed by reference in C++, the same value will directly change the value of the variable, and the value in the caller will also change accordingly.
Call the resolve_klass() function for class analysis. Generally speaking, the class analysis will be performed when the constant pool item is explained. This is introduced in the book "In-depth analysis of the Java virtual machine: source code analysis and detailed examples (basic volume)" , I need to say more here.
The implementation of the called resolve_klass() function and related functions is as follows:
void LinkResolver::resolve_klass(
KlassHandle& result,
constantPoolHandle pool,
int index,
TRAPS
) {
Klass* result_oop = pool->klass_ref_at(index, CHECK);
// 通过引用进行传递
result = KlassHandle(THREAD, result_oop);
}
Klass* ConstantPool::klass_ref_at(int which, TRAPS) {
int x = klass_ref_index_at(which);
return klass_at(x, CHECK_NULL);
}
int klass_ref_index_at(int which) {
return impl_klass_ref_index_at(which, false);
}
The implementation of the called impl\_klass\_ref\_index\_at() function is as follows:
int ConstantPool::impl_klass_ref_index_at(int which, bool uncached) {
int i = which;
if (!uncached && cache() != NULL) {
// 从which对应的ConstantPoolCacheEntry项中获取ConstantPoolIndex
i = remap_instruction_operand_from_cache(which);
}
assert(tag_at(i).is_field_or_method(), "Corrupted constant pool");
// 获取
jint ref_index = *int_at_addr(i);
// 获取低16位,那就是class_index
return extract_low_short_from_int(ref_index);
}
According to the assertion, the item at i in the original constant pool index must be JVM\_CONSTANT\_Fieldref, JVM\_CONSTANT\_Methodref, or JVM\_CONSTANT\_InterfaceMethodref. The format of these items is as follows:
CONSTANT_Fieldref_info{
u1 tag;
u2 class_index;
u2 name_and_type_index; // 必须是字段描述符
}
CONSTANT_InterfaceMethodref_info{
u1 tag;
u2 class_index; // 必须是接口
u2 name_and_type_index; // 必须是方法描述符
}
CONSTANT_Methodref_info{
u1 tag;
u2 class_index; // 必须是类
u2 name_and_type_index; // 必须是方法描述符
}
The format of the three items is the same. The item at the index of class\_index must be a CONSTANT\_Class\_info structure, representing a class or interface, and the current class field or method is a member of this class or interface. The name\_and\_type\_index index must be CONSTANT\_NameAndType\_info.
Get the index value of class_index by calling the int\_at\_addr() function and the extract\_low\_short\_from\_int() function. If you understand the constant pool memory layout, the implementation of this function will be very simple to understand, and it will not be here anymore. introduce.
Call the klass_at() function in the klass\_ref\_at() function. The implementation of this function is as follows:
Klass* klass_at(int which, TRAPS) {
constantPoolHandle h_this(THREAD, this);
return klass_at_impl(h_this, which, CHECK_NULL);
}
The implementation of the called klass\_at\_impl() function is as follows:
Klass* ConstantPool::klass_at_impl(
constantPoolHandle this_oop,
int which,
TRAPS
) {
CPSlot entry = this_oop->slot_at(which);
if (entry.is_resolved()) { // 已经进行了连接
return entry.get_klass();
}
bool do_resolve = false;
bool in_error = false;
Handle mirror_handle;
Symbol* name = NULL;
Handle loader;
{
MonitorLockerEx ml(this_oop->lock());
if (this_oop->tag_at(which).is_unresolved_klass()) {
if (this_oop->tag_at(which).is_unresolved_klass_in_error()) {
in_error = true;
} else {
do_resolve = true;
name = this_oop->unresolved_klass_at(which);
loader = Handle(THREAD, this_oop->pool_holder()->class_loader());
}
}
} // unlocking constantPool
// 省略当in_error变量的值为true时的处理逻辑
if (do_resolve) {
oop protection_domain = this_oop->pool_holder()->protection_domain();
Handle h_prot (THREAD, protection_domain);
Klass* k_oop = SystemDictionary::resolve_or_fail(name, loader, h_prot, true, THREAD);
KlassHandle k;
if (!HAS_PENDING_EXCEPTION) {
k = KlassHandle(THREAD, k_oop);
mirror_handle = Handle(THREAD, k_oop->java_mirror());
}
if (HAS_PENDING_EXCEPTION) {
...
return 0;
}
if (TraceClassResolution && !k()->oop_is_array()) {
...
} else {
MonitorLockerEx ml(this_oop->lock());
do_resolve = this_oop->tag_at(which).is_unresolved_klass();
if (do_resolve) {
ClassLoaderData* this_key = this_oop->pool_holder()->class_loader_data();
this_key->record_dependency(k(), CHECK_NULL); // Can throw OOM
this_oop->klass_at_put(which, k()); // 注意这里会更新常量池中存储的内容,这样就表示类已经解析完成,下次就不需要重复解析了
}
}
}
entry = this_oop->resolved_klass_at(which);
assert(entry.is_resolved() && entry.get_klass()->is_klass(), "must be resolved at this point");
return entry.get_klass();
}
The function first calls the slot\_at() function to obtain the value stored in a slot in the constant pool, and then uses CPSlot to represent this slot. There are two possible values stored in this slot, which point to the Symbol instance (because the class name uses CONSTANT The \_Utf8_info item indicates that the Symbol object is used in the virtual machine to represent the string) pointer and the pointer to the Klass instance. If the class has been explained, the last bit of the address represented by the pointer is 0. If it has not been parsed, then The last digit of the address is 1.
When there is no resolution, you need to call the SystemDictionary::resolve\_or\_fail() function to obtain an instance of the class Klass, and then update the information in the constant pool, so that you don't need to parse the class again next time. Finally, return the pointer to the Klass instance.
Continue back to the LinkResolver::resolve\_pool() function to see the next execution logic, that is, you will get the name\_and\_type\_index in the JVM\_CONSTANT\_Fieldref, JVM\_CONSTANT\_Methodref or JVM\_CONSTANT\_InterfaceMethodref item , Which points to the CONSTANT\_NameAndType\_info item, the format is as follows:
CONSTANT_NameAndType_info{
u1 tag;
u2 name_index;
u2 descriptor index;
}
The acquisition logic is to first find the index of the original constant pool item according to the index of the constant pool cache item, and then find CONSTANT\_NameAndType\_info, get the method name and signature index, and then get the name and signature of the called target method . This information will be used in the resolve\_virtual\_call() function to be called next.
02 resolve\_virtual\_call() function
The related functions called by the resolve\_virtual\_call() function are shown in the figure below.
The implementation of LinkResolver::resolve\_virtual\_call() is as follows:
void LinkResolver::resolve_virtual_call(
CallInfo& result,
Handle recv,
KlassHandle receiver_klass,
KlassHandle resolved_klass,
Symbol* method_name,
Symbol* method_signature,
KlassHandle current_klass,
bool check_access,
bool check_null_and_abstract,
TRAPS
) {
methodHandle resolved_method;
linktime_resolve_virtual_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, CHECK);
runtime_resolve_virtual_method(result, resolved_method, resolved_klass, recv, receiver_klass, check_null_and_abstract, CHECK);
}
First call the LinkResolver::linktime\_resolve\_virtual_method() function, this function will call the following functions:
void LinkResolver::resolve_method(
methodHandle& resolved_method,
KlassHandle resolved_klass,
Symbol* method_name,
Symbol* method_signature,
KlassHandle current_klass,
bool check_access,
bool require_methodref,
TRAPS
) {
// 从解析的类和其父类中查找方法
lookup_method_in_klasses(resolved_method, resolved_klass, method_name, method_signature, true, false, CHECK);
// 没有在解析类的继承体系中查找到方法
if (resolved_method.is_null()) {
// 从解析类实现的所有接口(包括间接实现的接口)中查找方法
lookup_method_in_interfaces(resolved_method, resolved_klass, method_name, method_signature, CHECK);
// ...
if (resolved_method.is_null()) {
// 没有找到对应的方法
...
}
}
// ...
}
The most important thing in the above function is to find a suitable method from the resolved\_klass class according to method\_name and method\_signature, and if found, assign it to the resolved\_method variable.
Call lookup\_method\_in\_klasses(), lookup\_method\_in\_interfaces() and other functions to search for methods, which will not be introduced here.
Next, let’s look at the runtime\_resolve\_virtual_method() function. The implementation of this function is as follows:
void LinkResolver::runtime_resolve_virtual_method(
CallInfo& result,
methodHandle resolved_method,
KlassHandle resolved_klass,
Handle recv,
KlassHandle recv_klass,
bool check_null_and_abstract,
TRAPS
) {
int vtable_index = Method::invalid_vtable_index;
methodHandle selected_method;
// 当方法定义在接口中时,表示是miranda方法
if (resolved_method->method_holder()->is_interface()) {
vtable_index = vtable_index_of_interface_method(resolved_klass,resolved_method);
InstanceKlass* inst = InstanceKlass::cast(recv_klass());
selected_method = methodHandle(THREAD, inst->method_at_vtable(vtable_index));
} else {
// 如果走如下的代码逻辑,则表示resolved_method不是miranda方法,需要动态分派且肯定有正确的vtable索引
vtable_index = resolved_method->vtable_index();
// 有些方法虽然看起来需要动态分派,但是如果这个方法有final关键字时,可进行静态绑定,所以直接调用即可
// final方法其实不会放到vtable中,除非final方法覆写了父类中的方法
if (vtable_index == Method::nonvirtual_vtable_index) {
selected_method = resolved_method;
} else {
// 根据vtable和vtable_index以及inst进行方法的动态分派
InstanceKlass* inst = (InstanceKlass*)recv_klass();
selected_method = methodHandle(THREAD, inst->method_at_vtable(vtable_index));
}
}
// setup result resolve的类型为CallInfo,为CallInfo设置了连接后的相关信息
result.set_virtual(resolved_klass, recv_klass, resolved_method, selected_method, vtable_index, CHECK);
}
When it is the miranda method, call the LinkResolver::vtable\_index\_of\_interface\_method() function to find; when it is the final method, because the final method cannot be overwritten by the subclass, so resolved\_method is the target calling method; remove After the first two cases, the remaining methods need to combine vtable and vtable\_index for dynamic allocation.
The above function will find all the information needed when calling and store it in the result variable of CallInfo type.
After all the information at the time of the call is obtained and stored in the CallInfo, the ConstantPoolCacheEntry can be filled according to the relevant information in the info. Let's look back at the execution logic of the InterpreterRuntime::resolve_invoke() function.
InterpreterRuntime::resolve_invoke() function part 2:
switch (info.call_kind()) {
case CallInfo::direct_call: // 直接调用
cache_entry(thread)->set_direct_call(
bytecode,
info.resolved_method());
break;
case CallInfo::vtable_call: // vtable分派
cache_entry(thread)->set_vtable_call(
bytecode,
info.resolved_method(),
info.vtable_index());
break;
case CallInfo::itable_call: // itable分派
cache_entry(thread)->set_itable_call(
bytecode,
info.resolved_method(),
info.itable_index());
break;
default: ShouldNotReachHere();
}
Regardless of direct call or dynamic dispatch of vtable and itable, the relevant information will be stored in the constant pool cache item after the method is resolved. Call the cache\_entry() function to obtain the corresponding ConstantPoolCacheEntry item, and then call the set\_vtable_call() function, this function will call the following function to update the information in the ConstantPoolCacheEntry item, as follows:
void ConstantPoolCacheEntry::set_direct_or_vtable_call(
Bytecodes::Code invoke_code,
methodHandle method,
int vtable_index
) {
bool is_vtable_call = (vtable_index >= 0); // FIXME: split this method on this boolean
int byte_no = -1;
bool change_to_virtual = false;
switch (invoke_code) {
case Bytecodes::_invokeinterface:
change_to_virtual = true;
// ...
// 可以看到,通过_invokevirtual指令时,并不一定都是动态分发,也有可能是静态绑定
case Bytecodes::_invokevirtual: // 当前已经在ConstantPoolCacheEntry类中了
{
if (!is_vtable_call) {
assert(method->can_be_statically_bound(), "");
// set_f2_as_vfinal_method checks if is_vfinal flag is true.
set_method_flags(as_TosState(method->result_type()),
( 1 << is_vfinal_shift) |
((method->is_final_method() ? 1 : 0) << is_final_shift) |
((change_to_virtual ? 1 : 0) << is_forced_virtual_shift), // 在接口中调用Object中定义的方法
method()->size_of_parameters());
set_f2_as_vfinal_method(method());
} else {
// 执行这里的逻辑时,表示方法是非静态绑定的非final方法,需要动态分派,则vtable_index的值肯定大于等于0
set_method_flags(as_TosState(method->result_type()),
((change_to_virtual ? 1 : 0) << is_forced_virtual_shift),
method()->size_of_parameters());
// 对于动态分发来说,ConstantPoolCacheEntry::_f2中保存的是vtable_index
set_f2(vtable_index);
}
byte_no = 2;
break;
}
// ...
}
if (byte_no == 1) {
// invoke_code为非invokevirtual和非invokeinterface字节码指令
set_bytecode_1(invoke_code);
} else if (byte_no == 2) {
if (change_to_virtual) {
if (method->is_public())
set_bytecode_1(invoke_code);
} else {
assert(invoke_code == Bytecodes::_invokevirtual, "");
}
// set up for invokevirtual, even if linking for invokeinterface also:
set_bytecode_2(Bytecodes::_invokevirtual);
}
}
After the connection is complete, the items in ConstantPoolCacheEntry are shown in the following figure.
So for invokevirtual, the method is distributed through the vtable. In the ConstantPoolCacheEntry, the \_f1 field is not used, and for the \_f2 field, if the non-final virtual method is called, the target method in the vtable is saved Index number. If it is a virtual final method, the _f2 field directly points to the Method instance of the target method.
Chapter 33-invokeinterface of method call instruction
The template of invokevirtual bytecode instruction is defined as follows:
def(Bytecodes::_invokeinterface , ubcp|disp|clvm|____, vtos, vtos, invokeinterface , f1_byte );
You can see that the command generation function is TemplateTable::invokeinterface(). In this function, the TemplateTable::prepare\_invoke() function will be called first. The assembly code generated by the TemplateTable::prepare\_invoke() function is as follows:
Part 1
0x00007fffe1022610: mov %r13,-0x38(%rbp)
0x00007fffe1022614: movzwl 0x1(%r13),%edx
0x00007fffe1022619: mov -0x28(%rbp),%rcx
0x00007fffe102261d: shl $0x2,%edx
// 获取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices
0x00007fffe1022620: mov 0x10(%rcx,%rdx,8),%ebx
// 获取ConstantPoolCacheEntry中indices[b2,b1,constant pool index]中的b1
// 如果已经连接,那这个b1应该等于185,也就是invokeinterface指令的操作码
0x00007fffe1022624: shr $0x10,%ebx
0x00007fffe1022627: and $0xff,%ebx
0x00007fffe102262d: cmp $0xb9,%ebx
// 如果invokeinterface已经连接就跳转到----resolved----
0x00007fffe1022633: je 0x00007fffe10226d2
The judgment logic of the assembly code is consistent with invokevirutal, so I won't explain it too much here.
Part 2
Since the method has not been parsed yet, the information in ConstantPoolCacheEntry needs to be set so that when you call it again, you don't need to find the information related to the call again. The generated assembly is as follows:
// 执行如下汇编代码时,表示invokeinterface指令还没有连接,也就是ConstantPoolCacheEntry中
// 还没有保存调用相关的信息
// 通过调用call_VM()函数生成如下汇编,通过这些汇编
// 调用InterpreterRuntime::resolve_invoke()函数
// 将bytecode存储到%ebx中
0x00007fffe1022639: mov $0xb9,%ebx
// 通过MacroAssembler::call_VM()来调用InterpreterRuntime::resolve_invoke()
0x00007fffe102263e: callq 0x00007fffe1022648
0x00007fffe1022643: jmpq 0x00007fffe10226c6
0x00007fffe1022648: mov %rbx,%rsi
0x00007fffe102264b: lea 0x8(%rsp),%rax
0x00007fffe1022650: mov %r13,-0x38(%rbp)
0x00007fffe1022654: mov %r15,%rdi
0x00007fffe1022657: mov %rbp,0x200(%r15)
0x00007fffe102265e: mov %rax,0x1f0(%r15)
0x00007fffe1022665: test $0xf,%esp
0x00007fffe102266b: je 0x00007fffe1022683
0x00007fffe1022671: sub $0x8,%rsp
0x00007fffe1022675: callq 0x00007ffff66ae13a
0x00007fffe102267a: add $0x8,%rsp
0x00007fffe102267e: jmpq 0x00007fffe1022688
0x00007fffe1022683: callq 0x00007ffff66ae13a
0x00007fffe1022688: movabs $0x0,%r10
0x00007fffe1022692: mov %r10,0x1f0(%r15)
0x00007fffe1022699: movabs $0x0,%r10
0x00007fffe10226a3: mov %r10,0x200(%r15)
0x00007fffe10226aa: cmpq $0x0,0x8(%r15)
0x00007fffe10226b2: je 0x00007fffe10226bd
0x00007fffe10226b8: jmpq 0x00007fffe1000420
0x00007fffe10226bd: mov -0x38(%rbp),%r13
0x00007fffe10226c1: mov -0x30(%rbp),%r14
0x00007fffe10226c5: retq
// 结束MacroAssembler::call_VM()函数
// 将invokeinterface x中的x加载到%edx中
0x00007fffe10226c6: movzwl 0x1(%r13),%edx
// 将ConstantPoolCache的首地址存储到%rcx中
0x00007fffe10226cb: mov -0x28(%rbp),%rcx
// %edx中存储的是ConstantPoolCacheEntry项的索引,转换为字节
// 偏移,因为一个ConstantPoolCacheEntry项占用4个字
0x00007fffe10226cf: shl $0x2,%edx
Similar to the implementation of invokevirtual, here we still call the InterpreterRuntime::resolve\_invoke() function to analyze the method when the method is not explained. We will also introduce the implementation of the InterpreterRuntime::resolve\_invoke() function in detail later.
After calling the resolve\_invoke() function, the information that the call believes will be stored in the CallInfo instance info. So at the end of the InterpreterRuntime::resolve\_invoke() function called, there will be the following implementation:
switch (info.call_kind()) {
case CallInfo::direct_call: // 直接调用
cache_entry(thread)->set_direct_call(
bytecode,
info.resolved_method());
break;
case CallInfo::vtable_call: // vtable分派
cache_entry(thread)->set_vtable_call(
bytecode,
info.resolved_method(),
info.vtable_index());
break;
case CallInfo::itable_call: // itable分派
cache_entry(thread)->set_itable_call(
bytecode,
info.resolved_method(),
info.itable_index());
break;
default: ShouldNotReachHere();
}
Vtable dispatch has been introduced before, now let’s look at itable dispatch.
When assigning to itable, the set\_itable\_call() function will be called to set the relevant information in ConstantPoolCacheEntry. The implementation of this function is as follows:
void ConstantPoolCacheEntry::set_itable_call(
Bytecodes::Code invoke_code,
methodHandle method,
int index
) {
InstanceKlass* interf = method->method_holder();
// interf一定是接口,method一定是非final方法
set_f1(interf); // 对于itable,则_f1为InstanceKlass
set_f2(index);
set_method_flags(as_TosState(method->result_type()),
0, // no option bits
method()->size_of_parameters());
set_bytecode_1(Bytecodes::_invokeinterface);
}
The information stored in ConstantPoolCacheEntry is:
- The bytecode is stored in the _f2 field, so that when this field has a value, it means that the method has been parsed;
- The \_f1 field stores the interface class of the declared method, that is, \_f1 is a pointer to the Klass instance representing the interface;
- \_f2 represents the index in the method table corresponding to the \_f1 interface class. If it is a final method, it stores a pointer to the Method instance.
After the analysis is complete, the items in ConstantPoolCacheEntry are shown in the following figure.
Part 3
If the invokeinterface bytecode instruction has been parsed, it will jump directly to resolved for execution. Otherwise, resolve_invoke will be called for analysis. After the analysis is complete, the logic at resolved will be executed, as follows:
// **** resolved ****
// resolved的定义点,到这里说明invokeinterface字节码已经连接
// 执行完如上汇编后寄存器的值如下:
// %edx:ConstantPoolCacheEntry index
// %rcx:ConstantPoolCache
// 获取到ConstantPoolCacheEntry::_f1
// 在计算时,因为ConstantPoolCacheEntry在ConstantPoolCache
// 之后保存,所以ConstantPoolCache为0x10,而
// _f1还要偏移0x8,这样总偏移就是0x18
0x00007fffe10226d2: mov 0x18(%rcx,%rdx,8),%rax
// 获取ConstantPoolCacheEntry::_f2属性
0x00007fffe10226d7: mov 0x20(%rcx,%rdx,8),%rbx
// 获取ConstantPoolCacheEntry::_flags属性
0x00007fffe10226dc: mov 0x28(%rcx,%rdx,8),%edx
// 执行如上汇编后寄存器的值如下:
// %rax:ConstantPoolCacheEntry::_f1
// %rbx:ConstantPoolCacheEntry::_f2
// %edx:ConstantPoolCacheEntry::_flags
// 将flags移动到ecx中
0x00007fffe10226e0: mov %edx,%ecx
// 从ConstantPoolCacheEntry::_flags中获取参数大小
0x00007fffe10226e2: and $0xff,%ecx
// 让%rcx指向recv
0x00007fffe10226e8: mov -0x8(%rsp,%rcx,8),%rcx
// 暂时用%r13d保存ConstantPoolCacheEntry::_flags属性
0x00007fffe10226ed: mov %edx,%r13d
// 从_flags的高4位保存的TosState中获取方法返回类型
0x00007fffe10226f0: shr $0x1c,%edx
// 将TemplateInterpreter::invoke_return_entry地址存储到%r10
0x00007fffe10226f3: movabs $0x7ffff73b63e0,%r10
// %rdx保存的是方法返回类型,计算返回地址
// 因为TemplateInterpreter::invoke_return_entry是数组,
// 所以要找到对应return type的入口地址
0x00007fffe10226fd: mov (%r10,%rdx,8),%rdx
// 获取结果处理函数TemplateInterpreter::invoke_return_entry的地址并压入栈中
0x00007fffe1022701: push %rdx
// 恢复ConstantPoolCacheEntry::_flags中%edx
0x00007fffe1022702: mov %r13d,%edx
// 还原bcp
0x00007fffe1022705: mov -0x38(%rbp),%r13
In the TemplateTable::invokeinterface() function, the prepare_invoke() function is called first, and the above assembly is generated by this function. The value of each register after the call is as follows:
rax: interface klass (from f1)
rbx: itable index (from f2)
rcx: receiver
rdx: flags
Then execute the assembly fragment generated by the TemplateTable::invokeinterface() function, as follows:
Part 4
// 将ConstantPoolCacheEntry::_flags的值存储到%r14d中
0x00007fffe1022709: mov %edx,%r14d
// 检测一下_flags中是否含有is_forced_virtual_shift标识,如果有,
// 表示调用的是Object类中的方法,需要通过vtable进行动态分派
0x00007fffe102270c: and $0x800000,%r14d
0x00007fffe1022713: je 0x00007fffe1022812 // 跳转到----notMethod----
// ConstantPoolCacheEntry::_flags存储到%eax
0x00007fffe1022719: mov %edx,%eax
// 测试调用的方法是否为final
0x00007fffe102271b: and $0x100000,%eax
0x00007fffe1022721: je 0x00007fffe1022755 // 如果为非final方法,则跳转到----notFinal----
// 下面汇编代码是对final方法的处理
// 对于final方法来说,rbx中存储的是Method*,也就是ConstantPoolCacheEntry::_f2指向Method*
// 跳转到Method::from_interpreted处执行即可
0x00007fffe1022727: cmp (%rcx),%rax
// ... 省略统计相关的代码
// 设置调用者栈顶并存储
0x00007fffe102274e: mov %r13,-0x10(%rbp)
// 跳转到Method::_from_interpreted_entry
0x00007fffe1022752: jmpq *0x58(%rbx) // 调用final方法
// **** notFinal ****
// 调用load_klass()函数生成如下2句汇编
// 查看recv这个oop对应的Klass,存储到%eax中
0x00007fffe1022755: mov 0x8(%rcx),%eax
// 调用decode_klass_not_null()函数生成的汇编
0x00007fffe1022758: shl $0x3,%rax
// 省略统计相关的代码
// 调用lookup_virtual_method()函数生成如下这一句汇编
0x00007fffe10227fe: mov 0x1b8(%rax,%rbx,8),%rbx
// 设置调用者栈顶并存储
0x00007fffe1022806: lea 0x8(%rsp),%r13
0x00007fffe102280b: mov %r13,-0x10(%rbp)
// 跳转到Method::_from_interpreted_entry
0x00007fffe102280f: jmpq *0x58(%rbx)
The above assembly contains the dispatch logic for final and non-final methods. For final methods, it is very simple because ConstantPoolCacheEntry::_f2 points to the called Method instance; for non-final methods, dynamic dispatch is required through vtable. The key assembly statement for dispatch is as follows:
mov 0x1b8(%rax,%rbx,8),%rbx
What needs to be reminded is that only a small number of methods may follow this logic for dynamic dispatch of vtables, such as calling methods in the Object class.
If you jump to notMethod, then you need to dynamically dispatch the method through itable. Let's take a look at the implementation logic of this part:
Part 5
// **** notMethod ****
// 让%r14指向本地变量表
0x00007fffe1022812: mov -0x30(%rbp),%r14
// %rcx中存储的是receiver,%edx中保存的是Klass
0x00007fffe1022816: mov 0x8(%rcx),%edx
// LogKlassAlignmentInBytes=0x03,进行对齐处理
0x00007fffe1022819: shl $0x3,%rdx
// 如下代码是调用如下函数生成的:
__ lookup_interface_method(rdx, // inputs: rec. class
rax, // inputs: interface
rbx, // inputs: itable index
rbx, // outputs: method
r13, // outputs: scan temp. reg
no_such_interface);
// 获取vtable的起始地址
// %rdx中存储的是recv.Klass,获取Klass中
// vtable_length属性的值
0x00007fffe10228c1: mov 0x118(%rdx),%r13d
// %rdx:recv.Klass,%r13为vtable_length,
// 最后r13指向第一个itableOffsetEntry
// 加一个常量0x1b8是因为vtable之前是InstanceKlass
0x00007fffe10228c8: lea 0x1b8(%rdx,%r13,8),%r13
0x00007fffe10228d0: lea (%rdx,%rbx,8),%rdx
// 获取itableOffsetEntry::_interface并与%rax比较,%rax中存储的是要查找的接口
0x00007fffe10228d4: mov 0x0(%r13),%rbx
0x00007fffe10228d8: cmp %rbx,%rax
// 如果相等,则直接跳转到---- found_method ----
0x00007fffe10228db: je 0x00007fffe10228f3
// **** search ****
// 检测%rbx中的值是否为NULL,如果为NULL,
// 那就说明receiver没有实现要查询的接口
0x00007fffe10228dd: test %rbx,%rbx
// 跳转到---- L_no_such_interface ----
0x00007fffe10228e0: je 0x00007fffe1022a8c
0x00007fffe10228e6: add $0x10,%r13
0x00007fffe10228ea: mov 0x0(%r13),%rbx
0x00007fffe10228ee: cmp %rbx,%rax
// 如果还是没有在itableOffsetEntry中找到接口类,
// 则跳转到search继续进行查找
0x00007fffe10228f1: jne 0x00007fffe10228dd // 跳转到---- search ----
// **** found_method ****
// 已经找到匹配接口的itableOffsetEntry,获取
// itableOffsetEntry的offset属性并存储到%r13d中
0x00007fffe10228f3: mov 0x8(%r13),%r13d
// 通过recv_klass进行偏移后找到此接口下声明
// 的一系列方法的开始位置
0x00007fffe10228f7: mov (%rdx,%r13,1),%rbx
We need to focus on the dispatch logic of itable. First, the following assembly is generated:
mov 0x118(%rdx),%r13d
What is stored in %rdx is recv.Klass. Get the value of the vtable_length attribute in Klass. With this value, we can calculate the size of the vtable and thus calculate the starting address of itable.
Then executed the following assembly:
lea 0x1b8(%rdx,%r13,8),%r13
The 0x1b8 represents the distance from the first address of recv.Klass to the vtable, so that the
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。