解密Java虚拟机学习笔记 1
前言:JVM的函数调用机制
JVM是用C和C++编写的一款软件,当JVM在调用java函数时,实际上执行了一段汇编代码,最终其实执行的是机器指令。(java code -> 字节码指令 -> 机器码)
JVM 从字节码解释器(效率低) 到 模板解释器 (JVM执行java函数 就是直接执行机器指令)。
JVM内部存在一个“边界”,边界外边是C程序,边界里面则直接跳转到机器码。 (C程序可以直接调用汇编指令。) 这个边界就是: 函数指针,就是 call_stub。
C 调用机器指令
main()函数中,通过int (*fun)(int)定义了一个函数指针fun,通过 fun = (void *)code 将该函数指针指向一个内存地址,即code数组的首地址,最后通过reslut = fun(7)调用。
JVM内部也有一个函数指针,那就是 call_stub。
call_stub 调用机制
1. call_stub 函数指针原型定义:
call_stub()函数 其实 就是 让函数指针指向了某个内存地址。
static CallStub call_stub() {
return CAST_TO_FN_PTR(CallStub, _call_stub_entry);
}
// CallStub 是一种自定义的类型
2. 解析
2.1 将宏替换
#define CAST_TO_FN_PTR(func_type, value) ((func_type) (castable_address(value)))
ps: 什么是 宏 ?
在 C 语言中,可以采用命令 #define 来定义宏。该命令允许把一个名称指定成任何所需的文本,例如一个常量值或者一条语句。在定义了宏之后,无论宏名称出现在源代码的何处,预处理器都会把它用定义时指定的文本替换掉。
替换后:
static CallStub call_stub() {
return ((CallStub) (castable_address(_call_stub_entry)));
}
2.2 CallStub是一种自定义的类型 函数指针类型
//CallStub是函数指针,指向的函数,8个入参,返回值类型 void
typedef void (*CallStub) {
address link,
intprt_t* result,
BasicType result_type,
methodOopDesc* method,
address entry_point,
intprt_t* parameters,
int size_of_parameters,
TRAPS
);
JVM内部调用 call_stub()
call_stub()函数原型声明,并没有入参,而JVM调用call_stub() 是却传入了8个参数 。why?
JVM 隐式的调用了函数指针。call_stub()函数最终返回的是一个函数指针的实例变量。
2.3 castable_address()
call_stub() 内部就是让函数指针指向了某个内存地址。
inline address_word castable_address(address x) {
return address_word(x);
}
address_word 自定义类型,表示 一种地址类型
typedef uintptr_t address_word;
uintprt_t 是 平台相关的, 在特定的平台上编译JVM时,编译器会自动根据平台类型,编译不同的hpp头文件。
Jvm 内部定义了3中:
- globalDefinitions_gcc.hpp —> linux
- globalDefinitions_aparcWorkd.hpp --> Macintosh
- globalDefinitions_visCPP.hpp ----> windows
// linux 平台 globalDefinitions_gcc.hpp
typedef usigned int uintprt_t;
整体替换后:
static CallStub call_stub() {
return ((CallStub) (unsigned int (_call_stub_entry)));
}
call_stub()函数的逻辑:
第一步,将_call_stub_entry 变量 转换为 usigned int 。
第二部,将_call_stub_entry 变量 转换为CallStub 自定义类型 (函数值针)。
2.4 _call_stub_entry
_call_stub_entry 本身是address 类型, 而该类型的原型是unsigned int.
// StubRoutines.hpp
static address _call_stub_entry;
回顾一下 JVM调用java函数的过程:
1.JVM 先调用 call_stub() 函数,返回是 CallStub类型,即函数指针。
2.JVM 将call_stub() 函数返回的 函数指针当成函数进行调用。
(call_stub() 直接返回_call_stub_entry,然后转换成函数指针,接着JVM就直接调用该函数指针)
_call_stub_entry 函数指针指向了哪里?
JVM在初始化的过程中,便将_call_stub_entry这一变量指向了某个内存地址。
2.5 CallStub()入参
CallStub是一个函数指针,并不是函数,最终JVM通过这一函数指针调用其所指向的函数。
1) 连接器link
link 其实在java函数的调用者与被调用者之间搭建了一座桥梁, 可以实现堆栈追踪,得到整个方法的调用链路。
在Java函数调用时,link指针被保存到当前方法的堆栈中。
连接器link 所属类型是 JavaCallWrapper,
2)method()
当前java方法在JVM内部的表示对象。
3) entry_point
4)parameters() 入参信息
5)size_of_parameters() 入参个数