0. 简介

众所周知,hotspot默认使用解释+编译混合(-Xmixed)的方式执行代码。它首先使用模板解释器对字节码进行解释,当发现一段代码是热点的时候,就使用C1/C2 JIT进行优化编译再执行,这也它的名字"热点"(hotspot)的由来。

解释器的代码位于hotspot/share/interpreter,它的总体架构如下:

1. 解释器的两种实现

首先hotspot有一个C++字节码解释器,还有一个模板解释器 ,默认使用的是模板解释器的实现。这两个有什么区别呢?举个例子,Java字节码有istore_0iadd,如果是C++字节码解释器(图右部分),那么它的工作流程就是这种:

void cppInterpreter::work(){
for(int i=0;i<bytecode.length();i++){
switch(bytecode[i]){
case ISTORE_0:
int value = operandStack.pop();
localVar[0] = value;
break;
case IADD:
int v1 = operandStack.pop();
int v2 = operandStack.pop();
int res = v1+v2;
operandStack.push(res);
break;
....
}
}
}

它使用C++语言模拟字节码的执行,iadd是两个数相加,字节码解释器从栈上pop两个数据然后求和,再push到栈上。

如果是模板解释器就完全不一样了。模板解释器是一堆本地码的例程(routines),它会在虚拟机创建的时候初始化好,也就是说,模板解释器在初始化的时候会申请一片内存并设置为可读可写可执行,然后向那片内存写入本地码。在解释执行的时候遇到iadd,就执行那片内存里面的二进制代码。

这种运行时代码生成的机制可以说是JIT,只是通常意义的JIT是指对一块代码进行优化再生成本地代码,同一段代码可能因为分成编译产出不同的本地码,具有动态性;而模板解释器是虚拟机在创建的时候JIT生成它自身,它的每个例程比如异常处理部分,安全点处理部分的本地码都是固定的,是静态的。

2. 解释器

2.1 抽象解释器

再回到主题,架构图有一个抽象解释器,这个抽象解释器描述了解释器的基本骨架,它的属性如下:

class AbstractInterpreter{
StubQueue* _code
address _slow_signature_handler;
address _entry_table[n];
address _cds_entry_table[n];
...
};

所有的解释器(C++字节码解释器,模板解释器)都有这些例程和属性,然后子类的解释器还可以再扩展一些例程。

我们重点关注_code,它是一个队列,

队列中的InterpreterCodelet表示一个小例程,比如iconst_1对应的代码,invokedynamic对应的代码,异常处理对应的代码,方法入口点对应的代码,这些代码都是一个个InterpreterCodelet...整个解释器都是由这些小块代码例程组成的,每个小块例程完成解释器的部分功能,以此实现整个解释器。

_entry_table也是个重要的属性,这个数组表示方法的例程,比如普通方法是入口点1_entry_table[0],带synchronized的方法是入口点2_entry_table[1],这些_entry_table[0],_entry_table[1]指向的就是之前_code队列里面的小块例程,就像这样:

_entry_table[0] = _code->get_stub("iconst_1")->get_address();
_entry_table[1] = _code->get_stub("fconst_1")->get_address();

当然实际的实现远比伪代码复杂。

2.2 模板解释器

前面说道小块例程组合起来实现了解释器,抽象解释器定义了必要的例程,具体的解释器在这之上还有自己的特设的例程。模板解释器就是一个例子,它继承自抽象解释器,在那些例程之上还有自己的特设例程:

  // 数组越界异常例程
static address _throw_ArrayIndexOutOfBoundsException_entry;
// 数组存储异常例程
static address _throw_ArrayStoreException_entry;
// 算术异常例程
static address _throw_ArithmeticException_entry;
// 类型转换异常例程
static address _throw_ClassCastException_entry;
// 空指针异常例程
static address _throw_NullPointerException_entry;
// 抛异常公共例程
static address _throw_exception_entry;

这样做的好处是可以针对一些特殊例程进行特殊处理,同时还可以复用代码。

到这里解释器的布局应该是说清楚了,我们大概知道了:解释器是一堆本地代码例程构造的,这些例程会在虚拟机启动的时候写入,以后解释就只需要进入指定例程即可。

3. 解释器生成器

3.1 生成器与解释器的关系

还有一个问题,这些例程是谁写入的呢?找一找架构图,下半部分都是解释器生成器,它的名字也是自解释的,那么它就是答案了。

前面刻意说道解释器布局就是想突出它只是一个骨架,要得到可运行的解释器还需要解释器生成器填充这个骨架。

解释器生成器本来可以独自完成填充工作,可能为了解耦,也可能是为了结构清晰,hotspot将字节码的例程抽了出来放到了templateTable(模板表)中,它辅助模板解释器生成器(templateInterpreterGenerator)完成各例程填充。

只有这两个还不能完成任务,因为组成模板解释器的是本地代码例程,本地代码例程依赖于操作系统和CPU,这部分代码位于hotspot/cpu/x86/中,所以

templateInterpreter =
templateTable +
templateTable_x86 +
templateInterpreterGenerator +
templateInterpreterGenerator_x86 +
templateInterpreterGenerator_x86_64

虚拟机中有很多这样的设计:在hotspot/share/的某个头文件写出定义,在源文件实现OS/CPU无关的代码,然后在hotspot/cpu/x86中实现CPU相关的代码,在hostpot/os实现OS相关的代码。

3.2 示例:数组越界异常例程生成

这么说可能有些苍白无力,还是结合代码更具说服力。

模板解释器扩展了抽象解释器,它有一个数组越界异常例程:

// 解释器生成器
// hotspot\share\interpreter\templateInterpreterGenerator.cpp
void TemplateInterpreterGenerator::generate_all() {
...
{ CodeletMark cm(_masm, "throw exception entrypoints");
// 调用CPU相关的代码生成例程
Interpreter::_throw_ArrayIndexOutOfBoundsException_entry = generate_ArrayIndexOutOfBounds_handler();
}
...
}
// 解释器生成器中CPU相关的部分
// hotspot\os\x86\templateInterpreterGenerator_x86.cpp
address TemplateInterpreterGenerator::generate_ArrayIndexOutOfBounds_handler() {
address entry = __ pc();
__ empty_expression_stack();
// rarg是数组越界的对象,rbx是越界的索引
Register rarg = NOT_LP64(rax) LP64_ONLY(c_rarg1);
__ call_VM(noreg,
CAST_FROM_FN_PTR(address,
InterpreterRuntime::
throw_ArrayIndexOutOfBoundsException),
rarg, rbx);
return entry;
}
// 解释器运行时
// hotspot\share\interpreter\interpreterRuntime.cpp
IRT_ENTRY(void, InterpreterRuntime::throw_ArrayIndexOutOfBoundsException(JavaThread* thread, arrayOopDesc* a, jint index))
ResourceMark rm(thread);
stringStream ss;
ss.print("Index %d out of bounds for length %d", index, a->length());
THROW_MSG(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), ss.as_string());
IRT_END

解释器生成器会调用CPU相关的generate_ArrayIndexOutOfBounds_handler()生成异常处理例程,里面有个call_VM,它调用了解释器运行时(InterpreterRuntime)来处理异常。解释器运行时是C++代码,

之所以用它是因为异常处理比较麻烦,还需要C++其他模块的支持(比如这里的stringStream和THROW_MSG),直接生成机器码会非常麻烦,我们可以调用解释器运行时相对轻松的处理。

我们在后面还会经常遇到call_VM调用解释器运行时这种模式,如果有很复杂的任务,需要其他C++模块的支持,那么它就派上用场了。

[Inside HotSpot] 模板解释器的更多相关文章

  1. HotSpot模板解释器目标代码生成过程源码分析

    虽然说解释执行模式是逐字逐句翻译给目标平台运行的,但这样的过程未免太过缓慢,如果能把字节码说的话做成纸条,运行时只要把对应的纸条交给目标平台就可以了,这样,执行速度就会明显提升.JVM的Hotspot ...

  2. [inside hotspot] 汇编模板解释器(Template Interpreter)和字节码执行

    [inside hotspot] 汇编模板解释器(Template Interpreter)和字节码执行 1.模板解释器 hotspot解释器模块(hotspot\src\share\vm\inter ...

  3. [Inside HotSpot] Java分代堆

    [Inside HotSpot] Java分代堆 1. 宇宙初始化 JVM在启动的时候会初始化各种结构,比如模板解释器,类加载器,当然也包括这篇文章的主题,Java堆.在hotspot源码结构中gc/ ...

  4. [Inside HotSpot] hotspot的启动流程与main方法调用

    hotspot的启动流程与main方法调用 虚拟机的使命就是执行public static void main(String[])方法,从虚拟机创建到main方法执行会经过一系列流程.这篇文章详细讨论 ...

  5. 【JVM源码解析】模板解释器解释执行Java字节码指令(上)

    本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...

  6. 【JVM】模板解释器--字节码的resolve过程

    1.背景 上文探讨了:[JVM]模板解释器--如何根据字节码生成汇编码? 本篇,我们来关注下字节码的resolve过程. 2.问题及准备工作 上文虽然探讨了字节码到汇编码的过程,但是: mov %ra ...

  7. 【JVM】模板解释器--如何根据字节码生成汇编码?

    1.背景 仅针对JVM的模板解释器: 如何根据opcode和寻址模式,将bytecode生成汇编码. 本文的示例中所使用的字节码和汇编码,请参见上篇博文:按值传递还是按引用? 2.寻址模式 本文不打算 ...

  8. [Inside HotSpot] C1编译器HIR的构造

    1. 简介 这篇文章可以说是Christian Wimmer硕士论文Linear Scan Register Allocation for the Java HotSpot™ Client Compi ...

  9. [Inside HotSpot] C1编译器优化:全局值编号(GVN)

    1. 值编号 我们知道C1内部使用的是一种图结构的HIR,它由基本块构成一个图,然后每个基本块里面是SSA形式的指令,关于这点如可以参考[Inside HotSpot] C1编译器工作流程及中间表示. ...

随机推荐

  1. sublime安装AngularJS插件

    sublime能够支持AngularJS开发那绝对是一件很爽的事情.下面我一步步讲解如何为sublime安装AngularJS插件. 1.添加控制包站点 根据你安装sublime 版本不同,在控制台写 ...

  2. 关于Flask-Login中session失效时间的处理

    最近需要使用Python开发web系统,主要用到的框架就是Flask,前端使用Jinja2模板引擎和Bootstrap,web容器使用Cherrypy,其中关于Login管理的使用了Flask-Log ...

  3. 排序系列 之 快速排序算法 —— Java实现

    基本思想: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变 ...

  4. I/O----复制文本文件

    文件 "我的青春谁做主.txt" 位于 D 盘根目录下,要求将此文件的内容复制到 C:/myPrime.txt 中. package io.day03; import java.i ...

  5. Java并发编程(五)锁的使用(下)

    显式锁 上篇讲了使用synchronized关键字来定义锁,其实Java除了使用这个关键字外还可以使用Lock接口及其实现的子类来定义锁,ReentrantLock类是Lock接口的一个实现,Reen ...

  6. BZOJ_2073_[POI2004]PRZ_状压DP

    BZOJ_2073_[POI2004]PRZ_状压DP 题意: 一只队伍在爬山时碰到了雪崩,他们在逃跑时遇到了一座桥,他们要尽快的过桥. 桥已经很旧了, 所以它不能承受太重的东西. 任何时候队伍在桥上 ...

  7. BZOJ_3343_教主的魔法_分块+二分查找

    BZOJ_3343_教主的魔法_分块+二分查找 题意:教主最近学会了一种神奇的魔法,能够使人长高.于是他准备演示给XMYZ信息组每个英雄看.于是N个英雄们又一次聚集在了一起,这次他们排成了一列被编号为 ...

  8. Github泄露扫描系统

    Github leaked patrol Github leaked patrol为一款github泄露巡航工具: 提供了WEB管理端,后台数据库支持SQLITE3.MYSQL和POSTGRES 双引 ...

  9. 你可能忽略的js类型转换

    前言 相信刚开始了解js的时候,都会遇到 2 == '2',但 1+2 == 1+'2'为false的情况.这时候应该会是一脸懵逼的状态,不得不感慨js弱类型的灵活让人发指,隐式类型转换就是这么猝不及 ...

  10. Hadoop2.41的HA的配置与启动

    我配置HA机制创建了7台虚拟机 1.修改Linux主机名2.修改IP3.修改主机名和IP的映射关系 ######注意######如果你们公司是租用的服务器或是使用的云主机(如华为云主机.阿里云主机等) ...