Java执行引擎工作原理:方法调用

Java执行引擎工作原理:方法调用

  • 方法调用如何实现
  • 函数指针和指针函数
  • CallStub源码详解
    Git链接(有HotSpot源码)

1 方法调用如何实现

计算机核心三大功能:方法调用、取指、运算

1.1 真实机器如何实现方法调用

  • 参数入栈。有几个参数就把几个参数入栈,此时入的是调用者自己的栈
  • 代码指针(eip)入栈。以便物理机器执行完调用函数之后返回继续执行原指令
  • 调用函数的栈基址入栈,为物理机器从被调用者函数返回做准备
  • 为调用方法分配栈空间,每个函数都有自己的栈空间。
// 一个进行求和运算的汇编程序
main:
    //保存调用者栈基地址,并为main()函数分配新栈空间
    pushl   %ebp
    movl    %esp, %ebp
    subl    $32,  %esp          //分配新栈空间,一共32字节

    //初始化两个操作数,一个是5,一个是3
    movl    $5,   20(%esp)
    movl    $3,   24(%esp)

    //将5和3压栈(参数入栈)
    movl    $24(%esp), %eax
    movl    %eax, 4(%esp)
    movl    20(%esp),  %eax
    movl    %eax, (%esp)
    
    //调用add函数
    calladd
    movl    %eax, 28(%esp)      //得到add函数返回结果

    //返回
    movl    $0,   %eax
    leave
    ret

add:
    //保存调用者栈基地址,并为add()函数分配新栈空间
    pushl   %ebp
    mov     %esp, %ebp
    subl    $16,  %esp

    //获取入参
    movl    12(%ebp), %(eax)
    movl    8(%ebp),  %(edx)

    //执行运算
    addl    %edx,  %eax
    movl    %eax,  -4(%ebp)

    //返回
    movl    -4(%ebp), %eax
    leave
    ret

我们先来了解一下栈空间的分配,在Linux平台上,栈是向下增长的,也就是从内存的高地址向低地址增长,所以每次调用一个新的函数时,新函数的栈顶相对于调用者函数的栈顶,内存地址一定是低方位的。

栈模型.png

完成add参数压栈后的main()函数堆栈布局
//初始化两个操作数,一个是5,一个是3
    movl    $5,   20(%esp)
    movl    $3,   24(%esp)

    //将5和3压栈(参数入栈)
    movl    $24(%esp), %eax
    movl    %eax, 4(%esp)
    movl    20(%esp),  %eax
    movl    %eax, (%esp)

完成add参数复制栈布局.png

  • 返回值约定保存在eax寄存器中
  • ebp:栈基地址
  • esp:栈顶地址
  • 相对寻址:28(%esp)相对于栈顶向上偏移28字节
  • 方法内的局部变量分配在靠近栈底位置,而传递的参数分配在靠近栈顶的位置
调用add函数时的函数堆栈布局

调用add的堆栈布局.png

  • 在调用函数之前,会自动向栈顶压如eip,以便调用结束后可正常执行原程序
  • 执行函数调用时,需要手动将ebp入栈
物理机器执行函数调用的主要步骤
  • 保存调用者栈基址,当前IP寄存器入栈
  • 调用函数时,在x86平台上,参数从右到左依次入栈
  • 一个方法所分配的栈空间大小,取决于该方法内部的局部变量空间、为被调用者所传递的入参大小
  • 被调用者在接收入参时,从8(%ebp)处开始,往上逐个获得每一个入参参数
  • 被调用者将返回的结果保存到eax寄存器中,调用者从该寄存器中获取返回值。

1.2 C语言函数调用

//一个简单的带参数求和函数调用
#include<stdio.h>
int add(int a, int b);
int main() {
	int a = 5;
	int b = 3;
	int c = add(a, b);
	return 0;
}
int add(int a,int b) {
	int z = 1 + 2;
	return z;
}
//main函数反汇编的代码
int main() {
//参数压栈、分配空间
002D1760  push        ebp       
002D1761  mov         ebp,esp  
002D1763  sub         esp,0E4h 
//以下部分代码不需要注意
002D1769  push        ebx  
002D176A  push        esi  
002D176B  push        edi  
002D176C  lea         edi,[ebp-0E4h]  
002D1772  mov         ecx,39h  
002D1777  mov         eax,0CCCCCCCCh  
002D177C  rep stos    dword ptr es:[edi]  
002D177E  mov         ecx,offset _F08B5E04_JVM1@cpp (02DC003h)  
002D1783  call        @__CheckForDebuggerJustMyCode@4 (02D120Dh)  

//main函数正式代码部分
	int a = 5;
002D1788  mov         dword ptr [a],5  
	int b = 3;
002D178F  mov         dword ptr [b],3  
	int c = add(a, b);
002D1796  mov         eax,dword ptr [b]  
	int c = add(a, b);
002D1799  push        eax  
002D179A  mov         ecx,dword ptr [a]  
002D179D  push        ecx  
002D179E  call        add (02D1172h)  
002D17A3  add         esp,8  
002D17A6  mov         dword ptr [c],eax  
	return 0;
002D17A9  xor         eax,eax  
}
//add函数汇编代码
int add(int a,int b) {
//参数压栈、分配空间
002D16F0  push        ebp  
002D16F1  mov         ebp,esp  
002D16F3  sub         esp,0CCh  
//一下部分代码不需要注意
002D16F9  push        ebx  
002D16FA  push        esi  
002D16FB  push        edi  
002D16FC  lea         edi,[ebp-0CCh]  
002D1702  mov         ecx,33h  
002D1707  mov         eax,0CCCCCCCCh  
002D170C  rep stos    dword ptr es:[edi]  
002D170E  mov         ecx,offset _F08B5E04_JVM1@cpp (02DC003h)  
002D1713  call        @__CheckForDebuggerJustMyCode@4 (02D120Dh)  

//add函数正式代码部分
	int z = 1 + 2;
002D1718  mov         dword ptr [z],3  
	return z;
002D171F  mov         eax,dword ptr [z]  
}
  • 其实这就是物理机器函数调用是的步骤
有参数传递场景下的C程序函数调用机制
  • 压栈
  • 参数传递顺序
  • 读取入参

JVM函数调用机制

//一个简单的求和函数
package cn.leishida;

public class Test {
	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;
	}
}
//编译成字节码的内容
Classfile /G:/workspace/JVM/src/cn/leishida/Test.class
  Compiled from "Test.java"
public class cn.leishida.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Methodref          #3.#16         // cn/leishida/Test.add:(II)I
   #3 = Class              #17            // cn/leishida/Test
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               add
  #12 = Utf8               (II)I
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java
  #15 = NameAndType        #5:#6          // "<init>":()V
  #16 = NameAn
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值