小仙女讲JVM(6)—字节码执行引擎

2024-03-11 03:59

本文主要是介绍小仙女讲JVM(6)—字节码执行引擎,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JVM中的执行引擎在执行java代码的时候,一般有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择。

栈帧
  • 定义:

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它位于虚拟机栈里面。

  • 作用:

每个方法从调用开始到执行完成的过程中,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

  • 特点:

(1)栈帧包括了局部变量表,操作数栈等,到底是需要多大的局部变量表,多深的操作数栈是在编译期确定的。因为一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响。

(2)两个栈帧之间的数据共享。在概念模型中,两个栈帧是完全独立的,但是在虚拟机的实现里会做一些优化处理,令两个栈帧出现一部分重叠。这样在进行方法调用时,就可以共用一部分数据,无须进行额外的参数复制传递。

(1)局部变量表

局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。

//方法参数   
max(int a,int b)

int a;//全局变量
void say(){int b=0;//局部变量}
  • 局部变量和类变量(用static修饰的变量)不同

类变量有两次赋初始值的过程:准备阶段(赋予系统初始值)和初始化阶段(赋予程序员定义的初始值)。所以即使在初始化阶段没有为类变量赋值也没关系,它仍然有一个确定的初始值。
但局部变量不一样,如果定义了,但没有赋初始值,是不能使用的。

(2)操作栈

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈、入栈操作。
在这里插入图片描述
例如,计算:

int a=2+3

操作数栈中最接近栈顶的两个元素是2和3,当执行iadd指令时,会将2和3出栈并相加,然后将相加的结果5入栈。

(3)动态链接

Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用分为两部分:

  • 静态解析:在类加载阶段或第一次使用的时候就转化为直接引用。
  • 动态链接:在每一次运行期间转化为直接引用。

(4)返回地址

当一个方法开始执行后,只有两种方式可以退出这个方法:正常退出、异常退出。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。

  • 当方法正常退出时

调用者的PC计数器作为返回地址。栈帧中一般会保存这个计数器值。

  • 当方法异常退出时

返回地址是要通过异常处理器表来确定的。栈帧中一般不会保存这部分信息。

方法调用

方法调用是确定调用哪一个方法。

(1)解析

对“编译器可知,运行期不可变”的方法进行调用称为解析。符合这种要求的方法主要包括

  • 静态方法,用static修饰的方法
  • 私有方法,用private修饰的方法

(2)分派

分派讲解了虚拟机如何确定正确的目标方法。分派分为静态分派和动态分派。讲解静动态分派之前,我们先看个多态的例子。

Human man=new Man();

在这段代码中,Human为静态类型,其在编译期是可知的。Man是实际类型,结果在运行期才可确定,编译期在编译程序的时候并不知道一个对象的实际类型是什么。

  • 静态分派:

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。它的典型应用是重载。

public class StaticDispatch{  static abstract class Human{  }  static class Man extends Human{}static class Woman extends Human{}public void say(Human hum){  System.out.println("I am human");  }  public void say(Man hum){  System.out.println("I am man");  }  public void say(Woman hum){  System.out.println("I am woman");  }  public static void main(String[] args){  Human man = new Man();  Human woman = new Woman();  StaticDispatch sr = new StaticDispatch();  sr.say(man);  sr.say(woman);  }  
}  

运行结果是:

I am human
I am human

为什么会产生这个结果呢?
因为编译器在重载时,是通过参数的静态类型而不是实际类型作为判断依据的。在编译阶段,javac编译器会根据参数的静态类型决定使用哪个重载版本,所以两个对say()方法的调用实际为sr.say(Human)。

  • 动态分派:

在运行期根据实际类型确定方法执行版本的分派过程。它的典型应用是重写。

public class DynamicDispatch{  static abstract class Human{  protected abstract void say();}  static class Man extends Human{@Overrideprotected abstract void say(){System.out.println("I am man");  }}static class Woman extends Human{@Overrideprotected abstract void say(){System.out.println("I am woman ");  }}public static void main(String[] args){  Human man = new Man();  Human woman = new Woman();  man.say();woman.say();man=new Woman();man.say();}  
}  

运行结果:

I am man
I am woman 
I am woman 

这似乎才是我们平时敲的java代码。对于方法重写,在运行时才确定调用哪个方法。由于Human的实际类型是man,因此调用的是man的name方法。其余的同理。
动态分派的实现依赖于方法区中的虚方法表,它里面存放着各个方法的实际入口地址。如果某个方法在子类中被重写了,那子类方法表中的地址将会替换为指向子类实现版本的入口地址,否则,指向父类的实现入口。

  • 单分派和多分派:

方法的接收者与方法的参数统称为方法的宗量,根据分派基于多少种宗量,分为单分派和多分派。
在静态分派中,需要调用者的实际类型和方法参数的类型才能确定方法版本,所以其是多分派类型。在动态分派中,已经知道了参数的实际类型,所以此时只需知道方法调用者的实际类型就可以确定出方法版本,所以其是单分派类型。综上,java是一门静态多分派,动态单分派的语言。

字节码解释执行引擎

虚拟机中的字节码解释执行引擎是基于栈的。下面通过一段代码来仔细看一下其解释的执行过程。

public int calc(){  int a = 100;  int b = 200;  int c = 300;  return (a + b) * c;  
}  

第一步:将100入栈。

第二步:将操作栈中的100出栈并存放到局部变量中。后面的200,300同理。

第三步:将局部变量表中的100复制到操作数栈顶。

第四步:将局部变量表中的200复制到操作数栈顶。

第五步:将100和200出栈,做整型加法,最后将结果300重新入栈。

第六步:将第三个数300从局部变量表复制到栈顶。接下来就是将两个300出栈,进行整型乘法,将最后的结果90000入栈。

第七步:方法结束,将操作数栈顶的整型值返回给此方法的调用者。

这篇关于小仙女讲JVM(6)—字节码执行引擎的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/796562

相关文章

什么是 Java 的 CyclicBarrier(代码示例)

《什么是Java的CyclicBarrier(代码示例)》CyclicBarrier是多线程协同的利器,适合需要多次同步的场景,本文通过代码示例讲解什么是Java的CyclicBarrier,感... 你的回答(口语化,面试场景)面试官:什么是 Java 的 CyclicBarrier?你:好的,我来举个例

Java使用Mail构建邮件功能的完整指南

《Java使用Mail构建邮件功能的完整指南》JavaMailAPI是一个功能强大的工具,它可以帮助开发者轻松实现邮件的发送与接收功能,本文将介绍如何使用JavaMail发送和接收邮件,希望对大家有所... 目录1、简述2、主要特点3、发送样例3.1 发送纯文本邮件3.2 发送 html 邮件3.3 发送带

Java实现数据库图片上传功能详解

《Java实现数据库图片上传功能详解》这篇文章主要为大家详细介绍了如何使用Java实现数据库图片上传功能,包含从数据库拿图片传递前端渲染,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、数据库搭建&nbsChina编程p; 3、后端实现将图片存储进数据库4、后端实现从数据库取出图片给前端5、前端拿到

Java实现将byte[]转换为File对象

《Java实现将byte[]转换为File对象》这篇文章将通过一个简单的例子为大家演示Java如何实现byte[]转换为File对象,并将其上传到外部服务器,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言1. 问题背景2. 环境准备3. 实现步骤3.1 从 URL 获取图片字节数据3.2 将字节数组

Java捕获ThreadPoolExecutor内部线程异常的四种方法

《Java捕获ThreadPoolExecutor内部线程异常的四种方法》这篇文章主要为大家详细介绍了Java捕获ThreadPoolExecutor内部线程异常的四种方法,文中的示例代码讲解详细,感... 目录方案 1方案 2方案 3方案 4结论方案 1使用 execute + try-catch 记录

SpringBoot接收JSON类型的参数方式

《SpringBoot接收JSON类型的参数方式》:本文主要介绍SpringBoot接收JSON类型的参数方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、jsON二、代码准备三、Apifox操作总结一、JSON在学习前端技术时,我们有讲到过JSON,而在

Spring-AOP-ProceedingJoinPoint的使用详解

《Spring-AOP-ProceedingJoinPoint的使用详解》:本文主要介绍Spring-AOP-ProceedingJoinPoint的使用方式,具有很好的参考价值,希望对大家有所帮... 目录ProceedingJoinPoijsnt简介获取环绕通知方法的相关信息1.proceed()2.g

Spring Security注解方式权限控制过程

《SpringSecurity注解方式权限控制过程》:本文主要介绍SpringSecurity注解方式权限控制过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、摘要二、实现步骤2.1 在配置类中添加权限注解的支持2.2 创建Controller类2.3 Us

Spring MVC跨域问题及解决

《SpringMVC跨域问题及解决》:本文主要介绍SpringMVC跨域问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录跨域问题不同的域同源策略解决方法1.CORS2.jsONP3.局部解决方案4.全局解决方法总结跨域问题不同的域协议、域名、端口

SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法

《SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法》本文主要介绍了SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法,具有一定的参考价值,感兴趣的可以了解一下... 目录方法1:更改IDE配置方法2:在Eclipse中清理项目方法3:使用Maven命令行在开发Sprin