深入理解java虚拟机(5)---字节码执行引擎

时间:2022-09-06 19:43:20

字节码是什么东西?

以下是百度的解释:

字节码(Byte-code)是一种包含执行程序、由一序列 op 代码/数据对组成的二进制文件。字节码是一种中间码,它比机器码更抽象。

它经常被看作是包含一个执行程序的二进制文件,更像一个对象模型。字节码被这样叫是因为通常每个 opcode 是一字节长,

但是指令码的长度是变化的。每个指令有从 0 到 255(或十六进制的: 00 到FF)的一字节操作码,被参数例如寄存器或内存地址跟随。

说了这么多,你可能还是不明白到底是什么东西。好吧,简单点,就是java编译以后的那个东东,“.class”文件。

所以class文件就是字节码文件,是由虚拟机执行的文件。也就是java语言和C & C++语言的区别就是,整个编译执行过程多了一个虚拟

机这一步。这个在“深入理解java虚拟机(3)---类的结构” 一文中已经解释,这是一个里程碑式的设计。上一节讲了虚拟机是如何加载

一个class的,这一节就讲解虚拟机是如何执行class文件的。

java虚拟机规范,规定了虚拟机字节码的执行概念模型。具体的虚拟机可以有不同的实现。

运行时栈帧结构

栈是每个线程独有的内存。

栈帧存储了局部变量表,操作数栈,动态连接,和返回地址等。

每一个方法的执行 对应的一个栈帧在虚拟机里面从如栈到出栈的过程。

只有位于栈顶的栈帧才有有效的,对应的方法称为当前方法。

执行引擎运行的所有指令只针对当前栈帧和当前方法。

1.局部变量表

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

在java编译成class的时候,已经确定了局部变量表所需分配的最大容量。

局部变量表的最小单位是一个Slot。

虚拟机规范没有明确规定一个Slot占多少大小。只是规定,它可以放下boolean,byte,...reference &return address.

reference 是指一个对象实例的引用。关于reference的大小,目前没有明确的指定大小。但是我们可以理解为它就是类似C++中的指针。

局部变量表的读取方式是索引,从0开始。所以局部变量表可以简单理解为就是一个表.

局部变量表的分配顺序如下:

this 引用。可以认为是隐式参数。

方法的参数表。

根据局部变量顺序,分配Solt。

一个变量一个solt,64为的占2个solt。java中明确64位的是long & double

为了尽可能的节约局部变量表,Solt可以重用。

注意:局部变量只给予分配的内存,没有class对象的准备阶段,所以局部变量在使用前,必须先赋值。

2.操作数栈

操作数栈在概念上很像寄存器。

java虚拟机无法使用寄存器,所以就有操作数栈来存放数据。

虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。

比如,iadd指令就要从操作数栈中弹出两个整数,执行加法运算,其结果又压回到操作数栈中,看看下面的示例,

它演示了虚拟机是如何把两个int类型的局部变量相加,再把结果保存到第三个局部变量的:

begin

iload_0 // push the int in local variable 0 onto the stack

iload_1 // push the int in local variable 1 onto the stack

iadd // pop two ints, add them, push result

istore_2 // pop int, store into local variable 2

end

操作数栈 的数据读取、写入就是出栈和如栈操作。

3.动态连接

每个栈帧都包含一个指向运行时常量池的引用,持有这个引用是为了支持动态连接。

符号池的引用,有一部分是在第一次使用或者初始化的时候就确定下来,这个称为静态引用。

还有一部分是在每次执行的时候采取确定,这个就是动态连接。

4.方法返回地址

方法只有2中退出方式,正常情况下,遇到return指令退出。还有就是异常退出。

正常情况:一般情况下,栈帧会保存 在程序计数器中的调用者的地址。虚拟机通过这个方式,执行方法调用者的地址,

然后把返回值压入调用者中的操作数栈。

异常情况:方法不会返回任何值,返回地址有异常表来确定,栈帧一般不存储信息。

5.方法调用

方法调用阶段不是执行该方法,而仅仅时确认要调用那个方法。class文件在编译阶段没有连接这一过程,、

所以动态连接这个在C++就已经有的技术,在java运用到了一个新的高度。所有的函数(除了私有方法,构造方法 & 静态方法,下同),理论上

都可以时C++里面的虚函数。所以所有的函数都需要通过动态绑定来确定“明确”的函数实体。

解析

所有方法调用的目标方法都是常量池中的符号引用。在类的加载解析阶段,会将一部分目标方法转化为直接引用。(可以理解为具体方法的直接地址)

可以转化的方法,主要为静态方法 & 私有方法。

Java虚拟机提供5中方法调用命令:

invokestatic:调用静态方法

invokespecial:调用构造器,私有方法和父类方法

invokevirtual:调用虚方法

invokeinterface:调用接口方法

invokedynamic:现在运行时动态解析出该方法,然后执行。

invokestatic & invokespecial 对应的方法,都是在加载解析后,可以直接确定的。所以这些方法为非虚方法。

java规定 final修饰的是一种非虚方法。

分派

静态分派

先看一个例子:

package com.joyfulmath.jvmexample.dispatch;

import com.joyfulmath.jvmexample.TraceLog;

/**
* @author deman.lu
* @version on 2016-05-19 13:53
*/
public class StaticDispatch {
static abstract class Human{ } static class Man extends Human{ } static class Woman extends Human{ } public void sayHello(Human guy)
{
TraceLog.i("Hello guy!");
} public void sayHello(Man man)
{
TraceLog.i("Hello gentleman!");
} public void sayHello(Woman man)
{
TraceLog.i("Hello lady!");
} public static void action()
{
Human man = new Man();
Human woman = new Woman();
StaticDispatch dispatch = new StaticDispatch();
dispatch.sayHello(man);
dispatch.sayHello(woman);
}
}
05-19 13:58:05.538 14881-14881/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello guy! [at (StaticDispatch.java:24)]
05-19 13:58:05.539 14881-14881/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello guy! [at (StaticDispatch.java:24)]

结果执行了public void sayHello(Human guy)函数。这不是应该多态吗?

Human man = new Man();

这里的Human我们理解为静态类型,后面的Man是实际类型。我们在编译器只知道静态类型,后面的实际类型等到动态连接的时候才知道。

所以对于sayHello方法,虚拟机在重载时,是通过参数的静态类型,而不是实际类型来判断使用那个方法的。

如果对类型做强制转换:

    public static void action()
{
Human man = new Man();
Human woman = new Woman();
StaticDispatch dispatch = new StaticDispatch();
dispatch.sayHello(man);
dispatch.sayHello(woman);
dispatch.sayHello((Man)man);
dispatch.sayHello((Woman)woman);
}
05-19 14:08:29.000 21838-21838/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello guy! [at (StaticDispatch.java:24)]
05-19 14:08:29.001 21838-21838/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello guy! [at (StaticDispatch.java:24)]
05-19 14:08:29.001 21838-21838/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello gentleman! [at (StaticDispatch.java:29)]
05-19 14:08:29.002 21838-21838/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello lady! [at (StaticDispatch.java:34)]

如果强转了以后,类型也跟着变化了。

静态分配的典型应用是方法重载。但是方法重载有时候不是唯一的,所以只能选合适的。

比如:

    public void sayHello(int data)
{
TraceLog.i("Hello int!");
} public void sayHello(long data)
{
TraceLog.i("Hello long");
}

当sayHello(1)的时候,一般情况下会调用int型的方法,但是如果注释调,只有long型的方法,long型参数方法就会被调用。

动态分派

上面讲的是重载,这里是重写(@Override)

package com.joyfulmath.jvmexample.dispatch;

import com.joyfulmath.jvmexample.TraceLog;

/**
* @author deman.lu
* @version on 2016-05-19 14:26
*/
public class DynamicDispatch {
static abstract class Human{
protected abstract void sayHello();
} static class Man extends Human{ @Override
protected void sayHello() {
TraceLog.i("Hello gentleman!");
}
} static class Woman extends Human{ @Override
protected void sayHello() {
TraceLog.i("Hello lady!");
}
} public static void action()
{
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}

先来看上面标红的这句:方法要解析man 的sayhello,问题是man是什么东西,我在解析的时候,是不知道的。所以“man.sayHello();”具体执行的那个类的方法,是需要在虚拟机

动态连接的时候才知道,这个就是多态。如果使用javap分析就可以知道这句话,在class文件里面是ynamicDispatch$Human: sayHello. 是的class文件不知道这个sayhello到底要去

调哪个方法。

invokevirtual指令解析的过程大概如下:首先在操作数栈里第一个元素的实际类型,即为C。

如果在类型C中找到与常量描述符相同的类名和方法,则权限校验通过后,即为找到该法方法,则返回这个方法的直接引用。

否则,对C的父类进行依次查找。

这个过程通俗一点就是,先从当前类里面寻找“同名”的该方法,如果没有,就从C的父类里面找,知道找到为止!

这个找到的方法,就是我们实际要调的方法。

如果找不到,就是exception。一般情况下,编译工具会帮我们避免这种情况。

单分派和多分派

概念上理解比较麻烦,说白了一点就是重载和重写都存在的情况:

package com.joyfulmath.jvmexample.dispatch;

import com.joyfulmath.jvmexample.TraceLog;

/**
* @author deman.lu
* @version on 2016-05-19 15:02
*/
public class MultiDispatch {
static class QQ{}
static class _360{} public static class Father{
public void hardChoice(QQ qq){
TraceLog.i("Father QQ");
} public void hardChoice(_360 aa){
TraceLog.i("Father 360");
}
} public static class Son extends Father{
public void hardChoice(QQ qq){
TraceLog.i("Son QQ");
} public void hardChoice(_360 aa){
TraceLog.i("Son 360");
}
} public static void action()
{
Father father = new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new QQ());
}
}
05-19 15:07:44.429 29011-29011/com.joyfulmath.jvmexample I/MultiDispatch$Father: hardChoice: Father 360 [at (MultiDispatch.java:19)]
05-19 15:07:44.429 29011-29011/com.joyfulmath.jvmexample I/MultiDispatch$Son: hardChoice: Son QQ [at (MultiDispatch.java:25)]

结果没有任何悬念,但是过程还是需要明确的。hardChoice的选择是在静态编译的时候就确认的。

而son.hardchoise 已经确认了函数的类型,只是需要进一步确认实体类型。所以动态连接是单分派。

动态语言支持:

使用C++语言可以定义一个调用方法:

void sort(int list[],const int size,int (*compare)(int,int));

但是java很难做到这一点,

void sort(List list,Compare c);Compare 一般要用接口实现。

在java 1.7 有一种方法可以支持该功能 MethodHandle。

这部分内容,由于我本地环境无法配置还调用,将会再后续更新。

铺垫了这么多,下面来讲讲字节码的执行

6.基于栈的字节码执行引擎

基于栈的指令集 和基于寄存器的指令集。

先看一个加法过程:

iconst_1

iconst_1

iadd

istore_0

这是基于栈的,也就是上文说的操作数栈。

先把2个元素要入栈,然后相加,放回栈顶,然后把栈顶的值存在slot 0里面。

基于寄存器的就不解释了。

基于寄存器 和基于栈的指令集现在都存在。所以很难说孰优孰劣。

基于栈的指令集 是和硬件无关的,而基于寄存器则依赖于硬件基础。基于寄存器在效率上优势。

但是虚拟机的出现,就是为了提供跨平台的支持,所以jvm的执行引擎是基于栈的指令集。

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

以下是javap的分析结果:

深入理解java虚拟机(5)---字节码执行引擎

以下图片描述了整个执行过程中代码,操作数栈,& 局部变量表的变化。

深入理解java虚拟机(5)---字节码执行引擎

深入理解java虚拟机(5)---字节码执行引擎

深入理解java虚拟机(5)---字节码执行引擎

这些过程只是一个概念模型,实际虚拟机会有很多优化的情况。

声明:本文相关图片来之参考书面,相关版权归原作者所有。

参考:

《深入理解java虚拟机》 周志明

  

深入理解java虚拟机(5)---字节码执行引擎的更多相关文章

  1. 深入理解Java虚拟机(字节码执行引擎)

    深入理解Java虚拟机(字节码执行引擎) 本文首发于微信公众号:BaronTalk 执行引擎是 Java 虚拟机最核心的组成部分之一.「虚拟机」是相对于「物理机」的概念,这两种机器都有代码执行的能力, ...

  2. 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性

    我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...

  3. 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的

    概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...

  4. 深入理解Java虚拟机读书笔记5----虚拟机字节码执行引擎

    五 虚拟机字节码执行引擎   1 运行时栈帧结构     ---栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素.     ---栈帧中存储了方法的局部变 ...

  5. 深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

    目录 1.类文件结构 1.1 Class类文件结构 1.2 魔数与Class文件的版本 1.3 常量池 1.4 访问标志 1.5 类索引.父索引与接口索引集合 1.6 字段表集合 1.7 方法集合 1 ...

  6. 深入理解JVM虚拟机5:虚拟机字节码执行引擎

    虚拟机字节码执行引擎   转自https://juejin.im/post/5abc97ff518825556a727e66 所谓的「虚拟机字节码执行引擎」其实就是 JVM 根据 Class 文件中给 ...

  7. Java虚拟机-字节码执行引擎

    概述 Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,成为各种虚拟机执行引擎的统一外观(Facade).不同的虚拟机引擎会包含两种执行模式,解释执行和编译执行. 运行时帧栈结构 栈帧(Sta ...

  8. Java虚拟机--虚拟机字节码执行引擎

    Java虚拟机--虚拟机字节码执行引擎 所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 用于支持虚拟机进行方法调用和方 ...

  9. java虚拟机字节码执行引擎

    定义 java虚拟机字节码执行引擎是jvm最核心的组成部分之一,它做的事情很简单:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果.在不同的虚拟机实现里,执行引擎在执行java代码 ...

随机推荐

  1. mysql中价格用什么数据类型表示最佳?

    DECIMAL和NUMERIC都行DECIMAL和NUMERIC类型在MySQL中视为相同的类型.它们用于保存必须为确切精度的值,例如货币数据.当声明该类型的列时,可以(并且通常要)指定精度和标度:例 ...

  2. css匹配原理与优化

    一. 匹配原理 浏览器CSS匹配不是从左到右进行查找,而是从右到左进行查找.比如之前说的 DIV#divBox p span.red{color:red;},浏览器的查找顺序如下:先查找 html 中 ...

  3. Orcale安装完成后 em管理、性能无法登陆 报:没有找到主机

    先在我的电脑环境变量中加入oracle_sid=orcl 在Orcale主目录中查找emd.properties 文件修改(时间格式) agentTZRegion=GMT agentTZRegion= ...

  4. 仿微博——MJExtension之字典转模型

    1.模型类中定义好属性 2.用AFN请求下来的数据保存到字典中 3.从字典中取出微博字典数组 //微博字典数组 NSArray *restrictArray = responseObject[@&qu ...

  5. php中strstr、strrchr、substr、stristr四个函数的区别总结

    php中strstr.strrchr.substr.stristr四个函数的区别总结 投稿:junjie 字体:[增加 减小] 类型:转载 时间:2014-09-22我要评论 这篇文章主要介绍了php ...

  6. Compiling Qt 5.5.1 (With Qtwebkit) With Visual Studio 2015

    I usually avoid writing articles about building a specific version of a software project but this ti ...

  7. matlab画棋盘格程序

    转载请注明出处:zhouyelihua**http://blog.csdn.net/zhouyelihua/article/details/46674191** 意义 在摄像机标定过程中经常须要打印棋 ...

  8. Java多线程编程实战读书笔记(一)

    多线程的基础概念本人在学习多线程的时候发现一本书——java多线程编程实战指南.整理了一下书中的概念制作成了思维导图的形式.按照书中的章节整理,并添加一些个人的理解.

  9. Java中对Array数组的常用操作

    目录: 声明数组: 初始化数组: 查看数组长度: 遍历数组: int数组转成string数组: 从array中创建arraylist: 数组中是否包含某一个值: 将数组转成set集合: 将数组转成li ...

  10. VSTO:使用C#开发Excel、Word【8】

    office加载项Office开发中使用的第二种模式是加载项模式.本书涵盖了几种Office加载项.其中包括Outlook的VSTO加载项,Excel和Word的COM加载项以及Excel的自动化加载 ...