JVM 01 走进JVM

1.1 走进 JVM


我们在刚刚学习 Java 的时候,就明确的知道,因为 JVM 的存在,才让 Java 可以一次编译到处运行。我们只需要把我们编写的字节码交给 JVM 去处理执行即可。完全不需要考虑不同平台的问题。那么 JVM 到底是撒呢?难道就只有这一个浅层的方面嘛。

VM:虚拟的跑操作系统
JVM:虚拟的跑程序

在这里插入图片描述

也正是得益于这种统一规范,除了 Java 之外,还有其他的 JVM 语言,比如 Kotlin、Groovy 等,它们的语法虽然和 Java 不一样,但是最终编译得到的字节码文件,和 Java 是同样的规范,同样可以交给 JVM 处理。(IDEA 反编译的时候,你会发现 跟 反编译 Java 的代码 一毛一样

在这里插入图片描述


1.2 技术概述

Java 虚拟机是 基于 栈的指令集架构

传统程序设计 一般都是 基于 寄存器的指令集架构

在这里插入图片描述

在这里插入图片描述

int main() {     //实现一个最简的a+b功能,并存入变量c
    int a = 10;
    int b = 20;
    int c = a + b;
    return c;
}
gcc -S main.c
  • x86 架构下的 C 语言汇编指令
	.file	"main.c"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc  ;rbp寄存器是64位CPU下的基址寄存器,和8086CPU的16位bp一样
	pushq	%rbp     ;该函数中需要用到rbp寄存器,所以需要先把他原来的值压栈保护起来
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp    ;rsp是64位下的栈指针寄存器,这里是将rsp的值丢给rbp,因为局部变量是存放在栈中的,之后会使用rbp来访问局部变量
	.cfi_def_cfa_register 6
	movl	$10, -12(%rbp)    ;10存入rbp所指向位置-12的位置 ->  int a = 10;
	movl	$20, -8(%rbp)     ;20存入rbp所指向位置-8的位置  -> int b = 20;
	movl	-12(%rbp), %edx   ;将变量a的值交给DX寄存器(32位下叫edx,因为是int,这里只使用了32位)
	movl	-8(%rbp), %eax    ;同上,变量b的值丢给AX寄存器
	addl	%edx, %eax        ;将DX和AX寄存器中的值相加,并将结果存在AX中  ->  tmp = a + b
	movl	%eax, -4(%rbp)    ;20存入rbp所指向位置-4的位置  -> int c = tmp;与上面合在一起就是int c = a + b;
	movl	-4(%rbp), %eax    ;根据约定,将函数返回值放在AX   -> return c;
	popq	%rbp     ;函数执行完毕,出栈
	.cfi_def_cfa 7, 8
	ret      ;函数返回
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 7.5.0-6ubuntu2) 7.5.0"
	.section	.note.GNU-stack,"",@progbits
  • arm 架构下
    .section   __TEXT,__text,regular,pure_instructions
   .build_version macos, 12, 0    sdk_version 12, 1
   .globl _main                           ; -- Begin function main
   .p2align   2
_main:                                  ; @main
   .cfi_startproc
; %bb.0:
   sub    sp, sp, #16                     ; =16
   .cfi_def_cfa_offset 16
   str    wzr, [sp, #12]
   mov    w8, #10
   str    w8, [sp, #8]
   mov    w8, #20
   str    w8, [sp, #4]
   ldr    w8, [sp, #8]
   ldr    w9, [sp, #4]
   add    w8, w8, w9
   str    w8, [sp]
   ldr    w0, [sp]
   add    sp, sp, #16                     ; =16
   ret
   .cfi_endproc
                                        ; -- End function
.subsections_via_symbols

我们发现 上述传统的都要依赖于 寄存器,而我们接下来的 Java 是没有依赖于寄存器的,而是更多的利用操作栈来完成一些功能。这样不仅设计和实现起来更简单,并且也能够更加方便地实现跨平台,不太依赖于硬件的支持。

public class Main {
    public int test(){    //和上面的例子一样
        int a = 10;
        int b = 20;
        int c = a + b;
        return c;
    }
}
javap -v target/classes/com/test/Main.class #使用javap命令对class文件进行反编译
  • Java 反汇编
...
public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10
         2: istore_1
         3: bipush        20
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: iload_3
        11: ireturn
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      12     0  this   Lcom/test/Main;
            3       9     1     a   I
            6       6     2     b   I
           10       2     3     c   I

bipush将 单个字节(1个字节)的常量值 推到栈顶。
istore_1将栈顶的int类型数值 存入到第二个本地变量。
istore_n将栈顶的int类型数值 存入到第n个本地变量。
iload_1将第二个本地变量存储的数值 推到栈顶。
iload_n将第n个本地变量存储的数值 推到栈顶。
iadd将栈顶的两个 int 类型变量相加,并将结果重新压入栈顶。
ireturn将栈顶的数值返回给方法。

JVM运行字节码时,所有的操作基本都是围绕两种数据结构,一种是堆栈(本质是栈结构),还有一种是队列,如果JVM执行某条指令时,该指令需要对数据进行操作,那么被操作的数据在指令执行前,必须要压到堆栈上,JVM会自动将栈顶数据作为操作数。如果堆栈上的数据需要暂时保存起来时,那么它就会被存储到局部变量队列上。

descriptor: ()I     //参数以及返回值类型,()I就表示没有形式参数,返回值为基本类型int
flags: ACC_PUBLIC   //public访问权限
Code:
  stack=2, locals=4, args_size=1    //stack表示要用到的最大栈深度,本地变量数,堆栈上最大对象数量(这里指的是this)
0: bipush        10     //0是程序偏移地址,然后是指令,最后是操作数
2: istore_1

指令前面的 数字是 程序的偏移地址,就是执行的这条指令,是哪个地方的。

实际上我们发现,JVM 执行命令的时候,基本都是 入栈出栈。而且大部分的指令还都没有 操作数,所以相比较来说,它要比 C语言编译出来的汇编指令,执行起来要更加的复杂,指令条数也会更多。所以 Java 的执行效率实际上是不如 C/C++ 的。

总结:虽然能实现跨平台,但是性能上却大打折扣!这也是为什么在 Android 上采用的是 定制版的 JVM,并且采用的还是 基于寄存器的指令集架构。此外,我们还知道 native 关键字 可以调用 C/C++ 编写的程序(通过JNI机制 来实现!),所以整体来看还是可以的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值