运行时数据区
本地方法栈
虚拟机栈
方法区
NativeMethod
VMStack
MethodArea
Stack

程序计数器
Heap
ProgramCounterRegister
本地
执行引擎
本地库接口
方法库
由所有线程共享的数据区
线程隔离的数据区

程序计数器(Program Counter Register)

是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
在Java虚拟机的概念模型里 [1] ,字节码解释器工作时就是通过改变这个计数器
的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处
理、线程恢复等基础功能都需要依赖这个计数器来完成。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地
址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。此内存区域是唯
一一个在《Java虚拟机规范》中没有规定任何OutOfM emoryError情况的区域。

Java虚拟机栈(Java Virtual M achine Stack)

Java虚拟机栈(Java Virtual M achine Stack)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧 [1] (Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

本地方法栈(Native M ethod Stacks)

与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。

方法区(M ethod Area)

方法区(M ethod Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载
的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然《Java虚拟机规范》中把
方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区
分开来。

运行时常量池(Runtime Constant Pool)

是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

执行引擎中的执行过程
image.png

image.png

image.png
image.png
同一个类被不同的类加载器加载,使用instance of 是不一样的

image.png

image.png

不正确的递归会导致stackoverflow 栈的空间有限,只入栈不出栈

image.png

image.png
image.png

image.png

image.png

image.png
image.png

整个专栏将分为四大模块。
1. 基本原理:剖析 Java 虚拟机的运行机制,逐一介绍 Java 虚拟机的设计决策以及工程实
现;
2. 高效实现:探索 Java 编译器,以及内嵌于 Java 虚拟机中的即时编译器,帮助你更好地
理解 Java 语言特性,继而写出简洁高效的代码;3. 代码优化:介绍如何利用工具定位并解决代码中的问题,以及在已有工具不适用的情况
下,如何打造专属轮子;
4. 虚拟机黑科技:介绍甲骨文实验室近年来的前沿工作之一 GraalVM。包括如何在 JVM
上高效运行其他语言;如何混搭这些语言,实现 Polyglot;如何将这些语言事前编译
(Ahead-Of-Time,AOT)成机器指令,单独运行甚至嵌入至数据库中运行。

Java 虚拟机具体是怎样运行 Java 字节码的?

下面我将以标准 JDK 中的 HotSpot 虚拟机为例,从虚拟机以及底层硬件两个角度,给你讲
一讲 Java 虚拟机具体是怎么运行 Java 字节码的。
从虚拟机视角来看,执行 Java 代码首先需要将它编译而成的 class 文件加载到 Java 虚拟
机中。加载后的 Java 类会被存放于方法区(Method Area)中。实际运行时,虚拟机会执
行方法区内的代码。
如果你熟悉 X86 的话,你会发现这和段式内存管理中的代码段类似。而且,Java 虚拟机同
样也在内存中划分出堆和栈来存储运行时数据。
不同的是,Java 虚拟机会将栈细分为面向 Java 方法的 Java 方法栈,面向本地方法(用
C++ 写的 native 方法)的本地方法栈,以及存放各个线程执行位置的 PC 寄存器。
image.png
在运行过程中,每当调用进入一个 Java 方法,Java 虚拟机会在当前线程的 Java 方法栈中
生成一个栈帧,用以存放局部变量以及字节码的操作数。这个栈帧的大小是提前计算好的,
而且 Java 虚拟机不要求栈帧在内存空间里连续分布。
当退出当前执行的方法时,不管是正常返回还是异常返回,Java 虚拟机均会弹出当前线程
的当前栈帧,并将之舍弃。从硬件视角来看,Java 字节码无法直接执行。因此,Java 虚拟机需要将字节码翻译成机器
码。
在 HotSpot 里面,上述翻译过程有两种形式:第一种是解释执行,即逐条将字节码翻译成
机器码并执行;第二种是即时编译(Just-In-Time compilation,JIT),即将一个方法中
包含的所有字节码编译成机器码后再执行。
image.png
前者的优势在于无需等待编译,而后者的优势在于实际运行速度更快。HotSpot 默认采用
混合模式,综合了解释执行和即时编译两者的优点。它会先解释执行字节码,而后将其中反
复执行的热点代码,以方法为单位进行即时编译。

Java 虚拟机的运行效率究竟是怎么样的?

HotSpot 采用了多种技术来提升启动性能以及峰值性能,刚刚提到的即时编译便是其中最
重要的技术之一。
即时编译建立在程序符合二八定律的假设上,也就是百分之二十的代码占据了百分之八十的
计算资源。
对于占据大部分的不常用的代码,我们无需耗费时间将其编译成机器码,而是采取解释执行
的方式运行;另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,以
达到理想的运行速度。理论上讲,即时编译后的 Java 程序的执行效率,是可能超过 C++ 程序的。这是因为与静
态编译相比,即时编译拥有程序的运行时信息,并且能够根据这个信息做出相应的优化。

举个例子,我们知道虚方法是用来实现面向对象语言多态性的。对于一个虚方法调用,尽管
它有很多个目标方法,但在实际运行过程中它可能只调用其中的一个。
这个信息便可以被即时编译器所利用,来规避虚方法调用的开销,从而达到比静态编译的
C++ 程序更高的性能。
为了满足不同用户场景的需要,HotSpot 内置了多个即时编译器:C1、C2 和 Graal。
Graal 是 Java 10 正式引入的实验性即时编译器,在专栏的第四部分我会详细介绍,这里暂
不做讨论。
之所以引入多个即时编译器,是为了在编译时间和生成代码的执行效率之间进行取舍。C1
又叫做 Client 编译器,面向的是对启动性能有要求的客户端 GUI 程序,采用的优化手段相
对简单,因此编译时间较短。
C2 又叫做 Server 编译器,面向的是对峰值性能有要求的服务器端程序,采用的优化手段
相对复杂,因此编译时间较长,但同时生成代码的执行效率较高。
从 Java 7 开始,HotSpot 默认采用分层编译的方式:热点方法首先会被 C1 编译,而后热
点方法中的热点会进一步被 C2 编译。
为了不干扰应用的正常运行,HotSpot 的即时编译是放在额外的编译线程中进行的。
HotSpot 会根据 CPU 的数量设置编译线程的数目,并且按 1:2 的比例配置给 C1 及 C2 编
译器。
在计算资源充足的情况下,字节码的解释执行和即时编译可同时进行。编译完成后的机器码
会在下次调用该方法时启用,以替换原本的解释执行。

openJDK里的asmtools介绍
http://hengyunabc.github.io/openjdk-asmtools/