先看一段代码
public class first{
public static void main(String[] args){
add(5,8);
}
public static int add(int a, int b){
int c = a + b;
int d = c + 9;
return d;
}
}
javac 后再用 javap -verbose first
{
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iconst_5 //看到个5,压栈
1: bipush 8 //看到个8
3: invokestatic #2 // 调用Method add:(II)I
6: pop
7: return
public static int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=2
0: iload_0 //栈读取变量
1: iload_1 //两个
2: iadd //明显add
3: istore_2
4: iload_2
5: bipush 9
7: iadd
8: istore_3 //返回值压栈
9: iload_3
10: ireturn
LineNumberTable:
line 6: 0
line 7: 4
line 8: 9
}
SourceFile: "first.java"
java源文件经过javac后转变为字节码文件,而字节码文件逻辑很容易看懂(关键是一个全局栈)
看看实际运行,字节码经过jvm转换为机器码(iload_0,iload_1,iadd等)
等价于前一章所说,注意,code[]由jvm动态生成
这就是JVM内部能够直接由C程序调用和执行机器指令的奥秘!而在JVM内部这个函数指针就是call_stub.
call_stub函数指针原型定义在stubRoutines.hpp中
/src/share/vm/runtime/stubRoutines.hpp(码云上搜索hotspot openjdk就有了)
static CallStub call_stub()
{
return CAST_TO_FN_PTR(CallStub, _call_stub_entry);
}
在JVM中,凡事出现函数名大写的情况,基本都是宏
#define CAST_TO_FN_PTR(func_type, value) ((func_type)(castable_address(value)))
call_stub()即是如下
// Calls to Java
typedef void (*CallStub)(
address link,
intptr_t* result,
BasicType result_type,
Method* method,
address entry_point,
intptr_t* parameters,
int size_of_parameters,
TRAPS
);
static CallStub call_stub(){
return (CallStub)(castable_address(_call_stub_entry));
}
CallStub是一个函数指针类型,返回值为void,参数有8个
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
//.............
// do call
{ JavaCallWrapper link(method, receiver, result, CHECK);
{ HandleMark hm(thread); // HandleMark used by HandleMarkCleaner
StubRoutines::call_stub()( //通过call_stub()返回函数指针再传入参数进行调用
(address)&link,
// (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
result_val_address, // see NOTE above (compiler problem)
result_type,
method(),
entry_point,
args->parameters(),
args->size_of_parameters(),
CHECK
);
result = link.result(); // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
// Preserve oop return value across possible gc points
if (oop_result_flag) {
thread->set_vm_result((oop) result->get_jobject());
}
}
} // Exit JavaCallWrapper (can block - potential return oop must be preserved)
//...........
}
call_stub()内部使用的castable_address()
inline address_word castable_address(address x) { return address_word(x) ; }
inline address_word castable_address(void* x) { return address_word(x) ; }
typedef uintptr_t address_word; // unsigned integer which will hold a pointer
// except for some implementations of a C++
// linkage pointer to function. Should never
// need one of those to be placed in this
// type anyway.
在linux平台上uintptr_t定义如下
typedef unsigned int uintptr_t;
也就是说
static CallStub call_stub(){
return (CallStub)(unsigned int(_call_stub_entry));
}
static address _call_stub_entry;
将_call_stub_entry转换为(unsigned int) 后再转换为(CallStub函数指针类型)
所以调用函数的实质在于将_call_stub_entry这个变量指向某个char code[]区域(后面会介绍)
下面简要介绍CallStub参数
link:
class JavaCallWrapper: StackObj {
friend class VMStructs;
private:
JavaThread* _thread; // the thread to which this call belongs
JNIHandleBlock* _handles; // the saved handle block
Method* _callee_method; // to be able to collect arguments if entry frame is top frame
oop _receiver; // the receiver of the call (if a non-static call)
JavaFrameAnchor _anchor; // last thread anchor state that we must restore
JavaValue* _result; // result value
public:
// Construction/destruction
JavaCallWrapper(methodHandle callee_method, Handle receiver, JavaValue* result, TRAPS);
~JavaCallWrapper();
// Accessors
JavaThread* thread() const { return _thread; }
JNIHandleBlock* handles() const { return _handles; }
JavaFrameAnchor* anchor(void) { return &_anchor; }
JavaValue* result() const { return _result; }
// GC support
Method* callee_method() { return _callee_method; }
oop receiver() { return _receiver; }
void oops_do(OopClosure* f);
bool is_first_frame() const { return _anchor.last_Java_sp() == NULL; }
};
大概就是_thread当前Java函数所在线程,_handles本地调用句柄_anchorJava线程的堆栈对象,
可以理解为被调用函数的上下文环境
method:
一切皆对象.每一个Java方法在被JVM加载时,JVM都会在内部为Java方法建立模型,类似Java里面的Method对象
- 函数名称,所属的类
- 参数信息
- 字节码信息
- ...
JVM在调用CallStub()函数指针式,将method()对象传递进去,最终就是为了从method()对象中获取到java函数的字节码信息,
而拿到字节码后便开始对字节码进行解析执行了.
entry_point
前面分析JVM通过_call_stub_entry所指向的地址,调用Java函数
在JVM通过_call_stub_entry调用Java方法之前,必须要先经过entry_point例程设置code地址(Maybe)
parameters
这个参数看名字就知道什么意思.用来描述Java方法的参数信息.在JVM为java方法分配堆栈式会将参数逐个入栈.一般会保存指针(引用)
size_of_parameters()
参数数量,即parameter个数
下一章_call_stub_entry例程