Java虚拟机执行引擎知识总结

执行引擎

也只有几个概念, JVM方法调用和执行的基础数据结构是 栈帧, 是内存区域中 虚拟机栈中的栈元素, 每一个方法的执行就对应着一个栈帧在虚拟机栈中出栈入栈的过程.

栈帧:则是包含有局部变量表, 操作数栈, 动态连接, 方法返回地址, 附加信息.

1 局部变量表:

存储单位是 slot, 一个slot占据32位, 对于64位的数据类型, 则是分配连续两个slot空间. 而对于一个非静态方法而言, 有一个隐藏参数, 为 this, 而在局部变量表中的变量存储顺序则是

this -> 方法参数 -> 方法体内的变量(slot可以重用, 超出作用域即可复用.) 方法在编译完成后, 其所需的空间已经确定.

(这里也是需要注意的一个地方, 变量的作用域常常会覆盖整个方法, 即使变量已经不再使用, 但只要还在作用域内, 其slot空间就无法给其他变量使用, 因此, 最好是在需要使用到变量时, 定义在合理的作用域范围内.)

2 操作数栈:

在操作数栈中需要注意,其数据类型必须与字节码指令的序列严格匹配.

3 动态连接: 稍后详解

4 方法返回地址:

方法有两种退出方式, 正常退出, 异常退出, 当正常退出后, 会恢复上层方法的局部变量表, 操作数栈, 并把方法返回结果压入调用者的操作数栈.

方法调用

方法调用阶段的唯一目的是, 确定调用方法的版本究竟是哪一个.

在Java虚拟机中提供了5条方法调用的相关指令:

invokestatic: 调用静态方法

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

invokevirtual: 调用所有的虚方法

invokeinterface: 调用所有的接口方法

invokedynamic: 先在运行时动态解析出调用点限定符所引用的方法, 然后再执行该方法.

虚方法是非虚方法的补集, 什么是非虚方法呢? 能够在编译器就确定将要调用的究竟是哪个方法, 进而将该方法的符号引用 转换为 相应的直接引用的 方法就被称作非虚方法.

我们知道在类加载时, 在相应的类信息中, 存有对应方法的相关信息, 常量池中存有相关直接引用. 在类加载的解析阶段, 即会将这部分的符号引用转换为直接引用.

那么什么方法才满足这种条件呢?

能够被invokespecial 和 invokestatic指令调用的方法, 都是可以在编译器确定的方法, 即静态方法, 私有方法, 父类方法(super.), 实例构造器.

在final方法是个特殊点, 虽然final方法的执行为 invokevirtual, 但它依然属于非虚方法, 不难理解, final方法不能够被重写.

方法分派(dispatch)

1 静态分派

对于代码

 Human man = new Man();

其中Human被称为变量的静态类型, 也叫外观类型, 而 Man则是变量的实际类型. 而一个变量的静态类型, 在声明时即已经确定, 仅仅在使用时才能够临时转换静态类型, 但变量本身的静态类型并不会改变, 实际类型的变化只有在运行期才能确定.

 //实际类型变化
 Human man = new Man();
 man = new Woman(); //静态类型的变化
 method((Man) man);
 method((Woman) man);

而当我们在重载方法时, 向方法中传入的参数类型, 即是静态类型.因此 重载是一种 可以在编译期就被确定执行方法版本 的行为.

2 动态分派

动态分派 与 重写息息相关.

 static class Human{   void sayHello() {
     System.out.println("human say hello");
   }
 } static class Man extends Human{   @Override
   void sayHello() {
     System.out.println("man say hello");
   }
 } void sayHello(Human man) {
   man.sayHello();
 } public static void main(String[] args) {
   Human man = new Man();
   Human human = new Human();   new Main().sayHello(man);   new Main().sayHello(human);
 } //out:
 man say hello
 human say hello

结果不必多做解释, 而现在的问题在于, 虚拟机如何知道, 究竟调用的是哪个方法?

 0: new      #3         // class Main$Man
 3: dup 4: invokespecial #4         // Method Main$Man."<init>":()V 7: astore_1 8: new      #5         // class Main$Human
 11: dup 12: invokespecial #6         // Method Main$Human."<init>":()V 15: astore_2 16: new      #7         // class Main 19: dup 20: invokespecial #8         // Method "<init>":()V 23: aload_1 24: invokevirtual #9         // Method sayHello:(LMain$Human;)V
 27: new      #7         // class Main 30: dup 31: invokespecial #8         // Method "<init>":()V 34: aload_2 35: invokevirtual #9         // Method sayHello:(LMain$Human;)V
 38: return

其中主要关注几个方法的执行点, invokespecial不用多说, 之前提到过, 是执行 构造器方法时 的指令

而 invokevirtual 则正是执行 main.sayHello(), 方法的指令, 指令的运行时解析过程大致如下:

而其中的关键点就在于, 取到的是 对象的实际类型.

1 找到操作数栈顶的第一个元素的所指对象的实际类型, 记做C

2 如果在C中找到与描述符 和 简单名称都相符的方法, 进行访问校验, 如果可以则返回方法的直接引用, 否则抛出 IllegalAccessError异常

3 否则按照继承关系 从下向上对C的各个父类进行第二步的搜索验证过程.

4 如果始终找不到, 抛出异常.

动态类型语言

这也是要提到的关于 invokedynamic指令的主要目的。

动态类型语言的概念是: 意思就是类型的检查是在运行时做的而非编译期。

而Java本身则是静态类型语言, 这一点又在哪里能够体现呢?

obj.println("language");

如果处在java环境中,且obj的静态语言类型是 java.io.PrintStream, 那么obj本身的实际类型也必须是PrintStream的子类才行, 哪怕本身存在 println方法也不可以, 但同样的问题放在 javascript中就不同了, 只要实际类型中存在println方法, 执行就不会有任何问题.

这点就是因为, java在编译时已经将其完整的符号引用生成出来, 如果注意到的话, 会发现无论是动态分派还是静态分派, 在编译的指令中都是已经精确到相应类的某一个方法中了, 如此, 自然只能够在有限的范围内略做调整, 如果超出了当前类的范围, 就无法调用了.

jvm虚拟机并不仅仅是java语言的虚拟机, 那么如何为动态类型语言提供支持就是一个问题了, 并且在目前java8中的lamda表达式中也应用的是 invokedynamic指令.

MethodHandle

而与之相关的jar包则是 java.lang.invoke, 相关的类则是 MethodHandle.

在这里我也并不想再谈 MethodHandle的使用方法, 网上资料实在不少.

需要提到的是, 它的功能和java的反射略有相似, 通过方法名, class, 就可以调用相应的方法. 但它比起反射要轻量级; 且Reflection是在模拟Java代码的调用, MethodHandle是在模仿字节码层面的调用.

这个方法不失为是在动态调用中除了反射之外的另一种选择.

基于栈解释器的执行过程

其实本文更像是在 前一篇博客中 java内存区域中的虚拟机栈的一种补充说明.

而真实的执行流程, 我想通过下文的代码来看:

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

-- javap -verbose Mainpublic int add();// 返回类型为 intdescriptor: ()Iflags: ACC_PUBLICCode://需要深度为2的操作数栈, 4个slot的局部变量空间, 有一个参数为 this
 stack=2, locals=4, args_size=1
 //将100推入操作数栈顶, 栈:100   0: bipush    100
  //将栈顶的数据出栈并存储到局部变量表的第一个slot中(从0开始)
  //此时:栈: - 局部变量表: slot1 100   2: istore_1   //与上面类似,重复过程   3: sipush    200   6: istore_2   7: sipush    300
   //此时:栈: - 局部变量表: slot1 100 slot2 200 slot3 300  10: istore_3  //将局部变量表 slot1的值复制到 栈顶  11: iload_1  //将局部变量表 slot2的值复制到 栈顶 此时:栈: 200 100  12: iload_2  //栈顶两个元素出栈, 并相加, 结果重新入栈. 此时: 栈: 300  13: iadd  //将局部变量表 slot3的值复制到 栈顶 此时:栈: 300 300  14: iload_3  //将栈顶元素相乘, 结果重新入栈  15: imul  //将栈顶的结果返回给方法调用者. 方法执行结束  16: ireturn LineNumberTable:
  line 85: 0
  line 86: 3
  line 87: 7
  line 88: 11

基于栈的执行引擎正是通过这样出栈入栈的方式完成指令, 而基于寄存器的则不然, 是将操作数存入寄存器, 同时将输入值也就是指令参数 与 某寄存器的存储值相加. 区别就在于存储位置, 以及参数问题, 基于栈的大部分指令都是无参数指令, 指令很明确的规定了 要用哪几个栈元素, 栈元素的类型是什么.

我们平常所使用的电脑, 其 X86指令集, 正是基于寄存器的指令集.

优缺点则是: 基于栈, 可移植性较强, 但速度比较慢, 慢的原因一是需要许多冗余操作, 代码. 二是基于栈是基于内存的操作方式, 而内存的速度比起寄存器更是要慢上许多.

总结

本文大致介绍这样几点:

java多态在 jvm层次的实现.

为什么说jvm执行引擎是基于栈的执行引擎, 以及究竟是怎样一个流程.

以上就是Java虚拟机执行引擎知识总结的详细内容,更多关于Java虚拟机执行引擎的资料请关注我们其它相关文章!

(0)

相关推荐

  • java虚拟机多线程进阶篇总结

    1.线程池基本参数 以Executors.newFixedThreadPool()这种创建方式为例: 大家想象,假如你创建一个线程池,你想这个池子有些什么参数呢?首先这个池子必须要有一个最大值:然后还希望这个池子的线程数量有一个警戒线,到了这个警戒线的位置说明线程池暂时已经满了,如果这个时候还有人过来拿线程,我们就要把这些人抓起来扔到一个地方去让他们排队,告诉他们:请稍等,等我们的线程有空闲的时候再来处理你的事:再然后假如人排队的地方都满了,玛德,好多人,于是线程池就想办法东拼西凑又多搞出来了几

  • java虚拟机中多线程总结

    我记得最开始接触多进程,多线程这一块的时候我不是怎么理解,为什么要有多线程啊?多线程到底是个什么鬼啊?我一个程序好好的就可以运行为什么要用到多线程啊?反正我是十分费解,即使过了很长时间我还是不是很懂,听别人说过也自己试过,但总是没有理解透彻: 时间过了很久感觉现在对多线程有了一点新的理解,我们还是从最基本的开始,顺便看看从jvm的角度看看多线程在jvm中是怎么分配内存的,顺便和前面的几篇内容串一下: 1.现实中的多线程 举个例子:假如你一个人在家,你现在听首歌5分钟,烧开水需要10分钟,玩一局游

  • Java虚拟机处理异常的最佳方式

    前言 欢迎来到Under The Hood专栏.本专栏旨在让Java开发人员一瞥在运行Java程序底层的神秘机制.本月的文章继续讨论Java虚拟机的字节码指令集,方法是检查Java虚拟机处理异常抛出和捕获的方式,包括相关的字节码.本文不讨论finally条款 - 这是下个月的主题.后续文章将讨论字节码系列的其他成员. 下面话不多说了,来一起看看详细的介绍吧 Exceptions Exceptions允许您顺利处理程序运行时发生的意外情况.要演示Java虚拟机处理异常的方式,请考虑一个名为NitP

  • 老生常谈Java虚拟机垃圾回收机制(必看篇)

    在Java虚拟机中,对象和数组的内存都是在堆中分配的,垃圾收集器主要回收的内存就是再堆内存中.如果在Java程序运行过程中,动态创建的对象或者数组没有及时得到回收,持续积累,最终堆内存就会被占满,导致OOM. JVM提供了一种垃圾回收机制,简称GC机制.通过GC机制,能够在运行过程中将堆中的垃圾对象不断回收,从而保证程序的正常运行. 垃圾对象的判定 我们都知道,所谓"垃圾"对象,就是指我们在程序的运行过程中不再有用的对象,即不再存活的对象.那么怎么来判断堆中的对象是"垃圾&q

  • java虚拟机学习高级篇

    还是继续说一下java虚拟机,为什么呢?因为我随意翻着别人的博客一不小心看到有关jvm的一点新的东西,挺有趣的,就按照我的理解分享一下: 还记得以前学过一首诗,"看成岭侧成峰,远近高低各不同",这一句诗的内在含义有的时候真的会让你猛然惊醒,进而如获至宝!的确,有的时候换一个角度看问题,你会发现不一样的世界. 我们平常学java的时候肯定涉及到了进程,多线程的概念,但是有没有想过操作系统也有进程和线程的概念,两者有关系吗?假如我们视角放高一点,以操作系统的角度看看一个java程序的运行,

  • Java虚拟机执行引擎知识总结

    执行引擎 也只有几个概念, JVM方法调用和执行的基础数据结构是 栈帧, 是内存区域中 虚拟机栈中的栈元素, 每一个方法的执行就对应着一个栈帧在虚拟机栈中出栈入栈的过程. 栈帧:则是包含有局部变量表, 操作数栈, 动态连接, 方法返回地址, 附加信息. 1 局部变量表: 存储单位是 slot, 一个slot占据32位, 对于64位的数据类型, 则是分配连续两个slot空间. 而对于一个非静态方法而言, 有一个隐藏参数, 为 this, 而在局部变量表中的变量存储顺序则是 this -> 方法参数

  • Java虚拟机JVM性能优化(一):JVM知识总结

    Java应用程序是运行在JVM上的,但是你对JVM技术了解吗?这篇文章(这个系列的第一部分)讲述了经典Java虚拟机是怎么样工作的,例如:Java一次编写的利弊,跨平台引擎,垃圾回收基础知识,经典的GC算法和编译优化.之后的文章会讲JVM性能优化,包括最新的JVM设计--支持当今高并发Java应用的性能和扩展. 如果你是一个开发人员,你肯定遇到过这样的特殊感觉,你突然灵光一现,所有的思路连接起来了,你能以一个新的视角来回想起你以前的想法.我个人很喜欢学习新知识带来的这种感觉.我已经有过很多次这样

  • 深入理解Java虚拟机_动力节点Java学院整理

    什么是Java虚拟机 Java程序必须在虚拟机上运行.那么虚拟机到底是什么呢?先看网上搜索到的比较靠谱的解释: 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java虚拟机有自己完善的硬体架构,如处理器.堆栈.寄存器等,还具有相应的指令系统.JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行. 这种解释应该算是正确的,但是只描述了虚拟机的外部行为和功能,并没有针对内部原理

  • java 虚拟机深入了解

    什么是Java虚拟机 作为一个Java程序员,我们每天都在写Java代码,我们写的代码都是在一个叫做Java虚拟机的东西上执行的.但是如果要问什么是虚拟机,恐怕很多人就会模棱两可了.在本文中,我会写下我对虚拟机的理解.因为能力所限,可能有些地方描述的不够欠当.如果你有不同的理解,欢迎交流. 我们都知道Java程序必须在虚拟机上运行.那么虚拟机到底是什么呢?先看网上搜索到的比较靠谱的解释: 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java虚拟机有自己完善的硬

  • 深入理解Java虚拟机体系结构

    1概述 众所周知,Java支持平台无关性.安全性和网络移动性.而Java平台由Java虚拟机和Java核心类所构成,它为纯Java程序提供了统一的编程接口,而不管下层操作系统是什么.正是得益于Java虚拟机,它号称的"一次编译,到处运行"才能有所保障. 1.1Java程序执行流程 Java程序的执行依赖于编译环境和运行环境.源码代码转变成可执行的机器代码,由下面的流程完成: Java技术的核心就是Java虚拟机,因为所有的Java程序都在虚拟机上运行.Java程序的运行需要Java虚拟

  • 解析Java虚拟机中类的初始化及加载器的父委托机制

    类的初始化 在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值. 在程序中,静态变量的初始化有两种途径: 1.在静态变量的声明处进行初始化: 2.在静态代码块中进行初始化. 没有经过显式初始化的静态变量将原有的值. 一个比较奇怪的例子: package com.mengdd.classloader; class Singleton { // private static Singleton mInstance = new Singleton();// 位置1 // 位置1输

  • 作为程序员必须掌握的Java虚拟机中的22个重难点(推荐0

    Java虚拟机一直是比较重要的知识点,是Java高级开发必会的.本文为你总结了关于JVM的22个重点.难点,图文并茂的向你展示和JVM有关的重点知识.全文共7000字左右. 概念 虚拟机:指以软件的方式模拟具有完整硬件系统功能.运行在一个完全隔离环境中的完整计算机系统 ,是物理机的软件实现.常用的虚拟机有VMWare,Visual Box,Java Virtual Machine(Java虚拟机,简称JVM). Java虚拟机阵营:Sun HotSpot VM.BEA JRockit VM.IB

  • 深入理解Java虚拟机 JVM 内存结构

    目录 前言 JVM是什么 JVM内存结构概览 运行时数据区 程序计数器 Java虚拟机栈 本地方法栈 方法区 运行时常量池 Java堆 直接内存 前言 JVM是Java中比较难理解和掌握的一部分,也是面试中被问的比较多的,掌握好JVM底层原理有助于我们在开发中写出效率更高的代码,可以让我们面对OutOfMemoryError时不再一脸懵逼,可以用掌握的JVM知识去查找分析问题.去进行JVM的调优.去让我们的应用程序可以支持更高的并发量等......总之一句话,学好JVM很重要! JVM是什么 J

随机推荐