jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

时间:2023-01-08 22:59:24

动态类型语言 

  动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期。

举例子解释“类型检查”,例如代码:

obj.println("hello world");

 假如这行代码是在Java语言中,并且变量obj的静态类型为java.io.PrintStream,那么变量obj的实际类型就必须是PrintStream的子类才是合法。否则,obj属于一个确实

有println(String)方法,单与PrintStream接口没有继承关系,代码依然不可能运行——因为类型检查不合法。是相同的代码在ECMAScript中情况不一样,无论obj是何种类型

只要这种类型的定义中确实包含有println(String)方法,那方法调用便可成功。

  这种差别产生的原因是Java语言在编译期已将println(String)方法完整的符号引用生成出来,作为方法调用指令参数存储到Class文件中,例如下面这段代码:

invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V

  这个符号引用包含了此方法定义在哪个具体类型之中、方法的名字以及参数顺序、参数类型和方法返回值等信息,通过这个符号引用,虚拟机可以翻译出这个方法的

直接引用。而在ECMAScript等动态类型语言中,变量obj本身是没有类型的,变量obj的值才是具有类型,编译时最多只能确定方法名称、参数、返回值这些信息,而不会

去确认方法所在的具体类型。“变量无类型而变量值才有类型”这个特点也是动态类型语言的一个重要特征。

  静态类型语言在编译期确定类型,最显著的好处是编译期可以提供严谨的类型检查,这样与类型相关的问题能在编码的时候就及时发现,利于稳定性及代码达到最大规模。

而动态类型语言在运行期确定类型,这可以为开发人员提供更大的灵活性,某些在静态类型语言中需要大量“臃肿”代码来实现的功能,由动态类型语言来实现可能会更加清晰

简洁,清晰和简洁通常也意味着开发效率的提升。

MethodHandle

  JDK1.7以前的字节码命令指令集中,4条方法调用指令(invokevirtual、invokespecial、invokestatic、invokeinterface)的第一个参数都是被调用的方法的符号引用,而

方法的符号引用在编译时产生,而动态类型语言只有在运行期。这样在Java虚拟机上实现的动态语言类型语言就不得不使用其他方式来实现。

java.lang.invoke包的主要目的是在之前单纯依靠符号引用来确定调用的目标方法这种方式以外,提供一种新的动态确定目标方法的机制,称为MethodHandle。

在拥有了Method Handle之后,Java语言也可以拥有类似函数指针或者委托的方法别名的工具了。

public class MethodHandleTest {
static class ClassA {
public void println(String s) {
System.out.println(s);
}
} public static void main(String[] args) throws Throwable {
Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA(); /*无论obj最终是哪个实现类,下面这句都能正确调用到println方法*/
getPrintlnMH(obj).invokeExact("icyfenix");
} private static MethodHandle getPrintlnMH(Object reveiver) throws Throwable {
/*MethodType :代表“方法类型”,包含了方法的返回值(methodType() 的第一个参数)和具体
参数(methodType()第二个及以后的参数)*/
MethodType mt = MethodType.methodType(void.class, String.class); /*lookup()方法来自于MethodHandles.lookup,这句的作用是在指定类中查找符合给定方法名称、方法类型,
* 并且符合调用权限的方法句柄*/ /*因为这里调用的是一个虚方法,按照Java语言的规则,方法第一个参数是隐式的,代表该方法的接受者,
也即使this指向的对象,这个参数以前是放在参数列表中进行传递的,而现在提供了bindTo()方法来完成这件事
*/
return MethodHandles.lookup().findVirtual(reveiver.getClass(), "println", mt).bindTo(reveiver);
}
}

  方法getPrintlnMH()模拟了invokevirtual指令的执行过程,只不过它的分派过程逻辑并非固话在Class文件的字节码上,而是通过一个方法来实现。这个方法本身的返回值(MethodHandle)

对象,可以视为对最终调用方法的一个“引用”。

  Java语言角度来看,MethodHandle的使用方法和效果与Reflection有相似之处,他们还是有区别:

  - Reflection 和 MtehodHandle机制都是在模拟方法调用,但Reflection是在模拟java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用

  - Reflection API的设计目标是只为Java语言服务的,而MehtodHandle则设计成客服务与所有Java虚拟机上的语言。其中包括Java语言。

 invokedynamic指令

  invokedynamin指令与MethodHandle机制的作用是一样的,都是为了解决原有4条“invoke*”指令方法分派规则固化在虚拟机之中的问题,如何查找目标方法的决定权从虚拟机转嫁到具体用户

代码之中,让用户有更高的*度。两者的思路是可类比的,可以把他们想象称为达成同一个目的,一个采用上层java代码和API来实现,另一个用字节码和Class中其他属性、常量来完成。

  每一处含有invokedynamic指令的位置都称作“动态调用点”,这条指令的第一个参数不再是代表方法引用的CONSTANT_Methodref_info常量,而是新加入的CONSTANT_InvokeDynamc_info

常量,从这个常量可以得到3项信息:引导方法、方法类型和名称。引导方法是固有参数,并且返回值是java.lang.invoke.CallSite对象,这个代表真正要执行的目标方法调用。根据

CONSTANT_InvokeDynamic_info常量提供的信息,虚拟机可找到并且执行引导方法,从而获得一个CallSite对象,最终调用要执行的目标方法。

方法分派规则

  invokedynamic指令与前4条“invoke*”指令的最大差别是它的分派逻辑不是由虚拟机决定的,而是由程序员决定。

基于栈的字节码解释执行引擎

  许多Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行)和编译执行(通过即使编译器产生本地代码执行)两种选择。

  解释执行

    传统编译原理中程序代码到目标机器代码的生成过程:

    jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

  Java语言中,Javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。因为这一部分动作是在Java虚拟机之外进行的

而解释器在虚拟机内部,所以Java程序的编译就是半独立的实现。

  基于栈的指令集与基于寄存器的指令集

    Java编译器输出的指令流,基本上是一种基于栈的指令集架构,指令流中的指令大部分都是零地址指令,它们依赖操作数栈进行工作与之相对的另外一套常用的指令集架构

是基于寄存器的指令集,最典型的就是x86的二进制指令集。

计算1+1的结果,基于栈的指令集:

iconst_1 //常量1压入栈
iconst_1 //常量1压入栈
iadd //把栈顶的两个值出栈、相加
istore_0 //结果放回栈顶

基于寄存器:

mov eax, 1  //EAX寄存器的值设置为1
add eax, 1 //把这个值加1,结果保存在eax寄存器里面

  基于栈的指令集主要的优点是可移植,寄存器由硬件直接提供,程序直接依赖这些硬件寄存器则不可避免地要收到硬件的约束。

使用站架构的指令集,用户程序不会直接使用这些寄存器,就可以由虚拟机实现来自行决定把一些访问频繁的数据放到寄存器中以获取尽量好的性能。栈架构指令集

代码相对更加紧凑、编译器实现更加简单等。

  栈架构指令主要缺点是执行速度相对来说要慢一些。所有主流物理机的指令集都是寄存器架构也从侧面印证了这点。虽然指令集的代码非常紧凑,但是完成相同

功能所需的指令数量一般会比寄存器架构多,因为出栈、入栈操作本身就产生了相当多的指令数量。

  栈实现在内存之中,频繁的栈访问也就意味着频繁的内存访问,相对于处理器来说,内存始终是执行速度的瓶颈。由于指令数量和内存访问的原因,所以导致了

栈架构指令集的执行速度会相对慢一些。

  基于栈的解释器执行过程

代码清单:

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

字节码指令:

    jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

执行过程中的代码、操作数栈和局部变量表的变化情况:

  jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

 jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

    jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

     jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

     jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

     jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

     jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

我们从这段程序执行中可以看出栈结构指令集的一般运行过程,整个运算过程的中间变量都以操作数的出栈、入栈为信息交换途径。

jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行的更多相关文章

  1. Net 4.0 之 Dynamic 动态类型

    Net 4.0 之 Dynamic 动态类型 本文主要旨在与网友分享.Net4.0的Dynamic 对Duck Type 的支持.     一..net4.0主要新特性 .Net4.0在.Net3.5 ...

  2. C#基本语法 - .Net 4.0 之 Dynamic 动态类型

      一..net4.0主要新特性 .Net4.0在.Net3.5基础上新增的主要特性有:可选参数.命名参数和Dynamic.具体请阅生鱼片的这篇博文.这里我们着重讲解C#4.0的Dynamic特性,对 ...

  3. Netty 源码(二)NioEventLoop 之 Channel 注册

    Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...

  4. 【.NET深呼吸】动态类型(扩充篇)

    前面两文中,老周已向大家介绍了关于动态类型对象的两种级别的使用方案,本篇呢,老周再讲一个自定义动态类型的例子. 前面给大家演示的例子中,动态类型中包装的是字典类型来存储数据的,这一次咱们换一种风味,老 ...

  5. Android笔记--View绘制流程源码分析(二)

    Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...

  6. 深入剖析PHP7内核源码(二)- PHP变量容器

    简介 PHP的变量使用起来非常方便,其基本结构是底层实现的zval,PHP7采用了全新的zval,由此带来了非常大的性能提升,本文重点分析PHP7的zval的改变. PHP5时代的ZVAL typed ...

  7. 基于栈的指令集与基于寄存器的指令集详细比对及JVM执行栈指令集实例剖析

    基于栈的指令集与基于寄存器的指令集详细比对: 这次来学习一些新的概念:关于Java字节码的解释执行的一种方式,当然啦是一些纯理论的东东,但很重要,在之后会有详细的实验来对理论进行巩固滴,下面来了解一下 ...

  8. JVM--a == (a = b)基于栈的解释器执行过程

    前言 在翻阅ConcurrentLinkedQueue的代码的时候,发现这样一段代码在JDK源码中总是出现. t != (t = tail) 原先总是以为这不就是 t != t ?很是纳闷,遂Demo ...

  9. JVM 内部原理(二)— 基本概念之字节码

    JVM 内部原理(二)- 基本概念之字节码 介绍 版本:Java SE 7 每位使用 Java 的程序员都知道 Java 字节码在 Java 运行时(JRE - Java Runtime Enviro ...

随机推荐

  1. Git命令之资源

    https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA% ...

  2. 在jfinal中使用druid,并配置查看权限

    首先导入druid包,然后配置configPlugin @Override public void configPlugin(Plugins me) { /**配置druid数据连接池插件**/ Dr ...

  3. node.js中使用https请求报CERT_UNTRUSTED的问题解决

    只要调用了没有受信的https就会报错:CERT_UNTRUSTED 简单的解决方法就是设置环境变量回避非授信证书的问题. 只要在请求的代码之前加上如下代码即可: process.env.NODE_T ...

  4. 没有jquery的时候,你看看这个

    vjs var br = (function() { var ua = navigator.userAgent.toLowerCase(); browser = { iPhone: /iphone/. ...

  5. 关于iChartjs在移动端提示框tip显示不正常的解决方法

    最近项目需要使用手机图表,但是找了很久都没找到专门为移动端开发的图表,只能找一些能兼容移动端的图表控件,今天就讲讲关于iChartjs这个图形库的一点问题. 问题 iChartjs的提示框tip的显示 ...

  6. Git SSH Key 生成步骤

    it是分布式的代码管理工具,远程的代码管理是基于ssh的,所以要使用远程的git则需要ssh的配置. github的ssh配置如下: 一 . 设置git的user name和email: $ git ...

  7. Llinux-apache安装

    第四章  构建LAMP网站服务平台 实验报告 1.安装apache服务器软件及相关组件 查看系统中是否安装apache服务相关的软件包: [root@www /]# rpm -qa | grep ht ...

  8. 【OS】NMON的简介和使用

    [OS]NMON的简介和使用 目前NMON已开源,以sourceforge为根据地,网址是http://nmon.sourceforge.net. 1. 目的 本文介绍操作系统监控工具Nmon的概念. ...

  9. cookie和sesssion

    一.cookie cookie和session都可以暂时保存在多个页面中使用的变量,但是它们有本质的差别. cookie存放在客户端浏览器中,session保存在服务器上.它们之间的联系是sessio ...

  10. C++ 自定义时间

      今天精神状态不好,和公司的领导请了假.为了抵抗我的痛苦,我在床上打坐冥想,从早上九点到下午三点二十六.嗯,感觉好多了.这种温和的暴力果然有效.   之后吃了点东西,然后无聊的我就在想,明天的工作该 ...