动态类型语言 

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

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

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代码的时候都有解释执行(通过解释器执行)和编译执行(通过即使编译器产生本地代码执行)两种选择。

  解释执行

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

    

  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 字节码执行 (二)动态类型支持与基于栈的字节码解释执行的更多相关文章

  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. Java 基于ArcFace人脸识别2.0 服务端Demo

    源代码传送:https://github.com/itboyst/ArcSoftFaceDemo 开发环境准备: ###开发使用到的软件和工具: Jdk8.mysql5.7.libarcsoft_fa ...

  2. JMeter写入文件

    之前我们推文讨论过如何使用jmeter读取文件, 比如csv, txt文件读取, 只要配置csv数据文件, 即可非常容易的从文件中读取想要的数据,  但是如果数据已经从API或者DB中获取, 想存放到 ...

  3. What is “Neural Network”

    Modern neuroscientists often discuss the brain as a type of computer. Neural networks aim to do the ...

  4. MVC 伪静态路由、MVC路由配置,实现伪静态。

    前段时间,研究了一下mvc路由配置伪静态,在网上扒了很多最后还是行不通,所以我现在把这些心得整理出来,供大家分享: 1.mvc中默认路由配置是:http://localhost:24409/Home/ ...

  5. JavaScript(四)变量

    变量的声明 在JavaScript程序中,使用一个变量之前应当使用关键字var进行声明,如下所示:var num;var sum; 也可以写成var num,sum,avg;如果只是声明变量而没有给变 ...

  6. WPF软件开发系统之六——药品管理查询系统

    本系统使用.Net WPF开发,运行于Windows操作系统,电脑或者触摸屏设备. 功能主要是药品按照各类条件检索及展示. 开发环境及工具: 首页: 按关键字查询: 按功效查询: 还有其它按拼音.按笔 ...

  7. linux的常用命令介绍

    1.ls  列出当前目录下的所有的文件和文件夹的名称. 参数如下:-a 显示隐藏文件 -l 显示方式为列表 -h 以可读性高的方式输出 eg: ls -lh /logs/tran  目录如果不指定(相 ...

  8. 进行Spark,Kafka针对Kerberos相关配置

    1. 提交任务的命令 spark-submit \--class <classname> \--master yarn \--deploy-mode client \--executor- ...

  9. MacOS 10.13.6 下装xcode 流程

    1.最好先安装brew https://github.com/Homebrew/brew/releases 自动安装脚本 /usr/bin/ruby -e "$(curl -fsSL htt ...

  10. 随心测试_软测基础_007<软测学习路线建议>

    如果你对软测感兴趣,那么如何学习软件测试呢? 贴心小提示:以下内容,仅供参考,不挖坑 1:学习方式_职业教育选择观 ————SX的观点:成本 | 收益说,理性接受 软测产业服务链中,测试工程师重在于& ...