我的编程空间,编程开发者的网络收藏夹
学习永远不晚

JAVA虚拟机(JVM)详细介绍(六)——字节码执行引擎

短信预约 信息系统项目管理师 报名、考试、查分时间动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

JAVA虚拟机(JVM)详细介绍(六)——字节码执行引擎

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

栈帧

定义:

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

作用:

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

20190618170100488.png

特点:

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

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

20190619194714705.jpg

(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{
            @Override
             protected abstract void say(){
             System.out.println("I am man");  
            }
    }
    static class Woman extends Human{
         @Override
             protected 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入栈。

20190621162245618.jpg

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

20190621162348556.jpg

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

20190621162353298.jpg

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

20190621162356788.jpg

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

20190621162405285.jpg

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

20190621162413216.jpg

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

2019062116242183.jpg

以上便是关于JAVA虚拟机——字节码执行引擎的全部介绍,更多相关问题请访问PHP中文网:JAVA视频教程

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

JAVA虚拟机(JVM)详细介绍(六)——字节码执行引擎

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

JAVA虚拟机(JVM)详细介绍(六)——字节码执行引擎

JVM中的执行引擎在执行java代码的时候,一般有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择。栈帧定义:栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它位于虚拟机栈里面。作用:每个方法从调用开始到执行完成的过程中,都
JAVA虚拟机(JVM)详细介绍(六)——字节码执行引擎
2014-07-03

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录