Java虚拟机(八)--方法调用(一)

方法作为程序的基本单元,作为原子指令的初步封装,计算机必须支持方法的调用。同样,Java语言的原子指令是字节码,Java方法是对字节码的封装,因此jvm必须支持对Java方法的调用。
JVM最后实际调用的并不是Java函数,而是对应的机器指令。

以汇编演示一个求和程序:

先是main函数

main:
	//保存调用者栈基地址,并为main函数分配新栈空间
	pushl %ebp
	movl %esp,%ebp
	sub $32,%esp  //分配新栈,给main函数分配32字节的空间 (新栈栈顶地址减32(地址从高位向地位方向分配))
	
	//初始化2个数据,一个是5,一个是3
	movl $5,20(%esp)  //放到操作数栈中
	movl $3,24(%esp) //放到操作数栈中
	
	//压栈
	movl 24(%esp),%eax  //从操作数栈中取出到寄存器中
	movl %eax,4(%esp)  //把寄存器中的数存到局部变量表中
	movl 20(%esp),%eax
	movl %eax,(%esp)

	//调用add方法
	call add
	movl %eax,28(%esp)  
	//返回
	movl $0,%eax
	leave
	ret

主要有以下几步:

  1. 保存栈基并分配新栈
  2. 初始化数据
  3. 压栈
  4. 函数调用
  5. 返回

add函数

add:
	//保存调用者栈基,并分配新栈
	pushl		%ebp
	movl %esp,%ebp
	sub $16,%esp	//给add函数分配16个字节的空间
	//获取入参
	movl12(%ebp), %eax  (当前方法栈上移12字节的位置)
	movl8(%ebp), %edx  (之所以是8,是因为main和add方法栈中间8个字节,
	其中4个字节用来保存调用方(这里就是main函数)下一条指令的地址,以及调用方(这里指main函数)的栈起始地址)
	//执行运算
	addl%edx,%eax  //把两个寄存器中的值求和,存到后一个寄存器
	mov%eax,-4(%ebp)  //把结果入栈
	//返回
	movl-4(%ebp),%eax
	leave
	ret

主要有以下几步:

  1. 保存调用者栈基地址并分配新栈
  2. 读取入参
  3. 执行运算
  4. 返回

给函数分配堆栈,有一个约定,就是分配的空间必须是16的倍数,也就是内存对齐。多了不退,少了要补齐。


再看看C程序

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:
	 pushl %ebp
	 movl%esp,%ebp
	 add$-16,%esp
	 sub$32.%esp

	movl$5,20(%esp)
	movl$3,24(%esp)
	movl24(%esp),%eax
	movl%eax,4(%ebp)
	movl20(%esp),%eax
	movl%eax,(%esp)

	calladd
	movl%eax,28(%esp)  //从eax寄存器取被调用方法的返回结果
	movl$0,%eax
	leave
	ret

add:
	pushl %ebp
	movl%esp,%ebp
	subl$16,%esp
	movl$3,-4(%ebp)
	movl-4(ebp),%eax
	leave
	ret

在真实的物理机器上,执行函数调用主要包含以下几个步骤:

  1. 保存调用者栈基地址,当前IP寄存器入栈
  2. 调用函数时,在x86平台,参数从右向左依次入栈
  3. 一个方法所分配的栈空间大小,取决于方法内部的局部变量空间、为被调用者所传入参大小
  4. 被调用者在接收入参时,从8(%ebp)处开始,往上逐个取入参
  5. 被调用者将返回结果保存在eax寄存器中,调用者从该寄存器中读取返回值

jvm的函数调用机制

前面演示了物理机器的函数调用机制和C语言函数的调用机制,下面讲述jvm的调用。
java源码:

package test;

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 /D:/项目/study/out/production/study/test/Test.class
  Last modified 2018-9-19; size 525 bytes
  MD5 checksum 73412e99dd6757092306da214d3a9207
  Compiled from "Test.java"
public class test.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#25         // java/lang/Object."<init>":()V
   #2 = Methodref          #3.#26         // test/Test.add:(II)I
   #3 = Class              #27            // test/Test
   #4 = Class              #28            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Ltest/Test;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               add
  #17 = Utf8               (II)I
  #18 = Utf8               a
  #19 = Utf8               I
  #20 = Utf8               b
  #21 = Utf8               c
  #22 = Utf8               d
  #23 = Utf8               SourceFile
  #24 = Utf8               Test.java
  #25 = NameAndType        #5:#6          // "<init>":()V
  #26 = NameAndType        #16:#17        // add:(II)I
  #27 = Utf8               test/Test
  #28 = Utf8               java/lang/Object
{
  public test.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ltest/Test;

  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
         1: bipush        8
         3: invokestatic  #2                  // Method add:(II)I
         6: pop
         7: return
      LineNumberTable:
        line 8: 0
        line 9: 7
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  args   [Ljava/lang/String;

  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   //常量0入栈
         1: iload_1 //常量1入栈
         2: iadd  //对上面2个数求和,并将结果入栈
         3: istore_2 //将栈顶的值(也就是求和的结果)存到局部变量表2(从0开始计数,2的位置是c的)的位置,
         4: iload_2		//把c的值加载到操作数栈
         5: bipush        9          //把常量9入栈
         7: iadd			//求和
         8: istore_3	//赋值给d
         9: iload_3  //加载d到栈顶
        10: ireturn //将栈顶值返回
      LineNumberTable:
        line 12: 0
        line 13: 4
        line 14: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0     a   I
            0      11     1     b   I
            4       7     2     c   I
            9       2     3     d   I
}
SourceFile: "Test.java"

上面的方法的字节码指令,最终会变成机器指令。而C语言调用机器指令是通过函数指针实现的。
jvm内部有一个函数指针–call_stub,这个函数指针是JVM内部C语言程序和机器指令的分水岭。
call_stub函数有8个参数

参数名含义
link连接器
result_val_address函数返回值地址
result_type函数返回类型
method()jvm内部表示的Java方法对象
entry_pointjvm调用Java函数的例程入口。jvm内部每一段历程都是在jvm启动过程中预先生成好的机器指令。要调用Java方法,必须经过本历程,即先执行这段机器指令,才能跳转到Java方法字节码所对应的机器指令去执行。
parametersJava方法的入参集合
size_of_parametersjava方法的入参数量
Check当前线程对象

call_stub函数做了:调用者栈帧保存、堆栈动态扩展、现场保存、Java函数参数压栈这一系列逻辑处理后,调用entry_point例程。
entry_point例程完成了Java主函数栈帧的创建,找到了Java主函数对应的第一个字节码并进入执行。
在hotspot内部,存在3种解释器,分别是字节码解释器,C++解释器和模板解释器。字节码解释器逐条翻译字节码指令,使用C/C++执行字节码指令逻辑,执行效率低下。
模板解释器相比于字节码指示器,高级之处在于直接把字节码指令翻译成对应的机器指令,高效的多。JVM默认使用模板解释器。

例程名作用
zerolocals一般Java方法调用,经过该例程
zerolocals同步Java方法调用,经过

方法在Jvm中的内存结构
在这里插入图片描述
右边是constMethod的数据结构,jvm内部基于偏移量来分配堆栈空间。
对于Java方法之间的调用,调用方的操作数栈和被调用方的局部变量表存在重叠区。所以在运行期,jvm只需要为Java方法的局部变量分配空间,而不需要为入参分配空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值