第一句子大全,网罗天下好句子,好文章尽在本站!

大牛巧用一文带你彻底搞懂解释器的内部构造和解释执行过程

时间:2008-09-27

在没有JIT编译器的情况下,解释器从某种程度上来说就是虚拟机本体,有关虚拟机的绝大部分问题都能在解释器中找到答案

友情提示:本文共有 3745 个字,阅读大概需要 8 分钟。

模板解释器

最简单的Java虚拟机可以只包括类加载器和解释器:类加载器加载字节码iconst_1、iconst_1、iadd并传给虚拟机,解释器按照字节码计算并得到结果。在没有JIT编译器的情况下,解释器从某种程度上来说就是虚拟机本体,有关虚拟机的绝大部分问题都能在解释器中找到答案。

本章将详细讨论解释器的内部构造和解释执行过程。

解释器体系

众所周知,HotSpot VM默认使用解释和编译混合(-Xmixed)的方式执行代码。首先它使用模板解释器对字节码进行解释,当发现一段代码是热点时,就使用C1或C2即时编译器优化编译后再执行,这也是它的名字——“热点”的由来。解释器的代码位于hotspot/share/interpreter,它的总体架构如图5-1所示。

HotSpot VM有一个C++字节码解释器,还有一个模板解释器(Template Interpreter),它们有很大的区别。

C++解释器行为

对于Java字节码istore_0和iadd来说,如果是C++字节码解释器(见图5-1右侧部分所示),那么它的工作流程如代码清单5-1所示。

代码清单5-1 C++字节码解释器伪代码

void cppInterpreter::work(){for(int i=0;i

模板解释器

行为模板解释器是一堆机器代码的例程,会在虚拟机创建时初始化好,换句话说,模板解释器在虚拟机初始化的时候为iadd和istore_0申请两片内存,并设置为可读、可写、可执行,然后向内存写入模拟iadd和istore_0执行的机器代码。在解释执行时遇到iadd,跳转到相应内存,并将该片内存的数据视作代码直接执行。

通常,JIT暗指即时编译器,但是JIT(Just-In-Time)这个词本身并没有编译器的含义,它只是表示“即时”,如果按照这个定义,JIT指运行时机器代码生成技术。在这个定义下,模板解释器也属于JIT范畴,因为根据上面的描述,它的各个组件如同各种字节码,异常处理、安全点处理等都是在虚拟机启动的时候动态生成机器代码,然后组成一个整体的。如果上面的描述太过抽象,可以参见代码清单5-2,它直观地说明了模板解释器是什么。

代码清单5-2 模板解释器

class TemplateInterpreter: public AbstractInterpreter {protected: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;static address _throw_StackOverflowError_entry;static address _remove_activation_entry;static address _remove_activation_preserving_args_entry;static EntryPoint _return_entry[number_of_return_entries];static EntryPoint _earlyret_entry;static EntryPoint _deopt_entry[number_of_deopt_entries];static address _deopt_reexecute_return_entry;static EntryPoint _safept_entry;static DispatchTable _active_table;static DispatchTable _normal_table;static DispatchTable _safept_table;static address _wentry_point[DispatchTable::length];...};TemplateInterpreter包含各种机器代码入口,例如字节码对应的机器代码模板(_normal_table)、退优化的机器代码(_deopt_entry)、常见异常发生时的机器代码(_throw_X)。除此之外,TemplateInterpreter继承自AbstractInterpreter,也包含一些机器代码入口,如代码清单5-3所示。

代码清单5-3 抽象解释器

class AbstractInterpreter: AllStatic {protected:static StubQueue* _code;static bool _notice_safepoints;static address _native_entry_begin;static address _native_entry_end;// 方法入口点static address _entry_table[number_of_method_entries];static address _cds_entry_table[number_of_method_entries];static address _slow_signature_handler;static address _rethrow_exception_entry;...};如代码清单5-3所示,抽象解释器中包含普通方法入口的机器代码(_entry_table)、CDS方法入口的机器代码(_cds_entry_table)、第4章提到的处理解释器与JNI调用约定的机器代码(_slow_signature_handler)等。_entry_table等价于代码清单5-1中的for-switch,也就是说,模板解释器把“遍历方法字节码然后逐个执行”这一过程也写成了机器代码。

机器代码片段

上面的TemplateInterpreter和AbstractInterpreter包含各种机器代码片段,它们构成解释器本体。机器代码片段的生成是由TemplateInterpreterGenerator完成的,它是解释器本体的生成器。关于重要入口机器代码的生成过程将在本章后面详细描述,这里我们关心的是生成的机器代码片段,它们都会放入桩代码队列(_code),如代码清单5-4所示。

代码清单5-4 桩代码队列

class StubQueue: public CHeapObj {private:StubInterface* _stub_interface; // 沟通Stub和StubQueue的接口address _stub_buffer; // 存放机器的地方(buffer)int _buffer_size; // buffer大小int _buffer_limit; // buffer大小限制int _queue_begin; // 队列开始int _queue_end; // 队列结束int _number_of_stubs; // 机器代码片段个数Mutex* const _mutex;public:StubQueue::StubQueue(...) : _mutex(lock) {intptr_t size = align_up(buffer_size, 2*BytesPerWord);BufferBlob* blob = BufferBlob::create(name, size);if( blob == NULL) {vm_exit_out_of_memory(...);}_stub_interface = stub_interface;_buffer_size = blob->content_size();_buffer_limit = blob->content_size();_stub_buffer = blob->content_begin();_queue_begin = 0;_queue_end = 0;_number_of_stubs = 0;}};StubQueue是code/stubs中的一个结构。它抽象出一个存放机器代码片段的队列,当模板解释器的生成器生成机器代码时会将代码片段放入该队列。StubQueue只是一个队列抽象,真正存放机器代码的片段是_stub_buffer,它由BufferBlob::create()创建。

CodeCache

在HotSpot VM中,除了模板解释器外,有很多地方也会用到运行时机器代码生成技术,如广为人知的C1编译器产出、C2编译器产出、C2I/I2C适配器代码片段、解释器到JNI适配器的代码片段等。为了统一管理这些运行时生成的机器代码,HotSpot VM抽象出一个CodeBlob体系,由CodeBlob作为基类表示所有运行时生成的机器代码,并衍生出五花八门的子类:

1)CompiledMethod:编译后的Java方法。

a)nmethod:JIT编译后的Java方法。

b)AOTCompiledMethod:AOT编译的方法。

2)RuntimeBlob:非编译后的代码片段。

a)BufferBlob:解释器等使用的代码片段。

AdapterBlob:C2I/I2C适配器代码片段。

VtableBlob:虚表代码片段。

MethodHandleAdapterBlob:MethodHandle代码片段。

b)RuntimeStub:调用运行时方法的代码片段。

c)SingletonBlob:单例代码片段。

DeoptimizationBlob:退优化代码片段。

ExceptionBlob:异常处理代码片段。SafepointBlob:错误指令异常处理代码片段。

UncommonTrapBlob:打破编译器假设的稀有情况代码片段。

前面提到过C2I/I2C适配器代码片段,它们就存放在AdapterBlob中。解释器到JNI的调用约定适配器代码片段和模板解释器一样,都存放在BufferBlob中。前面进行分类是为了区分代码片段的类型,而统一管理这些即时生成的机器代码片段的区域是CodeCache,由虚拟机将所有CodeBlob都放入CodeCache。

第4章曾提到Threads::create_vm会初始化线程和组件,CodeCache便是这里所说的组件之一,它在Threads::create_vm初始化主线程后初始化,如代码清单5-5所示。

代码清单5-5 CodeCache初始化

void CodeCache::initialize() {... // 开启分段CodeCache,将运行时生成的代码片段按类别放到三个区域if (SegmentedCodeCache) {initialize_heaps();} else {... // 不开启分段CodeCache,所有运行时生成的代码片段都放到一个区域add_heap(rs, "CodeCache", CodeBlobType::All);}// 初始化指令缓存刷新模块(ICache Flush)icache_init();// * Windows上为CodeCache中的运行时生成的代码注册结构化异常处理(SEH)os::register_code_area((char*)low_bound(), (char*)high_bound());}CodeCache区域的最大空间可以用-XX:ReservedCodeCacheSize=指定。

Java 9在JEP 197中引入了CodeCache分段。如果没有开启CodeCache分段,JVM会用一个区域存放所有运行时生成的代码片段。

如果使用-XX:+SegmentedCodeCache开启分段,JVM会将CodeCache内

部拆分为三个区域,分别用于存放非nmethod代码片段(如解释器、C2I/I2C适配器等)、处于分层编译的2和3级别带Profiling信息的nmethod、处于分层编译的1和4级别不带Profiling信息的nmethod。

CodeCache分段有很多好处,比如:

分隔非nmethod方法,例如带Profiling的nmethod与不带Profiling的nmethod,可以根据需要访问不同的区域,无须每次遍历整个CodeCache。

提升程序运行时尤其是GC的性能。在开启分段堆后GC扫描根只需要遍历一个区域。

提升代码局部性,因为相同类型的代码很有可能在最近一段时间被频繁访问。

指令缓存刷新

模板解释器和JIT编译器都重度依赖运行时代码生成技术,它们在运行时向内存写入数据,这些数据可以被当作指令执行。CPU和主存间一般有L1、L2、L3三级高速缓存,L1级高速缓存又可以分为指令缓存(Instruction Cache)和数据缓存(Data Cache),这样划分后CPU可以同时获取指令和数据,进而提升性能,但是也带来了一致性问题。

处理器只能执行位于指令缓存中的指令,不能直接将数据缓存中的数据视作指令来执行。同时处理器只能看到位于数据缓存中的数据,不能直接访问内存。因为不能直接修改指令缓存和内存,所以会出现如图5-2所示的情况。

处理器未来会自动将数据缓存的数据写回内存,然后指令缓存重新读取位于内存的指令,但是没有办法知道处理器何时这样做。举个例子,如果虚拟机运行时生成了新的代码想要立即执行它们,处理器可能会忽略它们执行旧的代码,因为旧的代码仍然位于指令缓存中。观察图片的箭头不难知道,要解决这个问题需要强制将数据缓存中的新数据先写回内存,然后载入指令缓存,如图5-3所示。

要想执行新的指令,可以强制刷新指令缓存的数据,使缓存的指令无效化,这时处理器会主动将数据缓存中的数据写入内存,然后读取内存的新指令到指令缓存。HotSpot VM中无效化指令缓存的操作由runtime/icache模块完成,CodeCache区域初始化后会调用icache_init()初始化指令缓存刷新模块,如代码清单5-6所示。

代码清单5-6 指令缓存清理的实现

void ICacheStubGenerator::generate_icache_flush(...) {...// 如果待清理的内存地址为0,则跳过清理__ testl(lines, lines);__ jcc(Assembler::zero, done);// 禁止CPU指令重排序(只能使用mfence屏障)__ mfence();// 否则清理[0,ICache::line_size]内存地址范围内的缓存行__ bind(flush_line);__ clflush(Address(addr, 0)); // 底层是x86的clflush实现__ addptr(addr, ICache::line_size);__ decrementl(lines);__ jcc(Assembler::notZero, flush_line);// 禁止CPU指令重排序__ mfence();// 清理完成__ bind(done);__ ret(0);*flush_icache_stub = (ICache::flush_icache_stub_t)start;}x86上指令缓存刷新是由clflush指令实现的,该指令是唯一一个必须配合使用mfence的指令。

本文给大家讲解的内容是详细讨论解释器的内部构造和解释执行过程

下篇文章给大家讲解的是详解探讨模板解释器,解释器的生成;觉得文章不错的朋友可以转发此文关注小编;感谢大家的支持!

本文如果对你有帮助,请点赞收藏《大牛巧用一文带你彻底搞懂解释器的内部构造和解释执行过程》,同时在此感谢原作者。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。
相关阅读
一文搞懂 JVM 字节码执行引擎

一文搞懂 JVM 字节码执行引擎

...详细讲解方法调用的类型2.3.1 解析调用笔者之前在 一夜搞懂 | JVM 类加载机制中就谈到过解析,感觉有点混淆的,可以回去看下特点:是静态过程在编译期间就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变为可...

2012-05-04 #经典句子

让人醒悟的一句话经典语录 人活着的意义应当在过程 而不是结论

让人醒悟的一句话经典语录 人活着的意义应当在过程 而不是结论

...惨的思想,我们就会凄惨。十四 * 要想爱情和婚姻美满,巧用致命的吸引力:男人温柔,女人撒娇。十五 * 要知道两个人是否默契,就看他们一起配合干活儿时,聊天越多越默契,聊天越少越不默契,因为他(她)需要分很大精...

2023-11-03 #经典句子

「GCTT 出品」关于结构化并发的笔记——Go 语言中有害的声明语句

「GCTT 出品」关于结构化并发的笔记——Go 语言中有害的声明语句

...则”:如果一个控制结构具有这种形状,那么在你不关心内部发生的细节的上下文中,你可以忽略[stuff happen]部分,并把整个作为规则的顺序流程。而且更好的是,任何由这些部分组成的代码也是如此。当我看这个代码时:print("...

2023-09-18 #经典句子

边界扩展效应中的情景和语义记忆过程:使用记忆/知道范式的调查

边界扩展效应中的情景和语义记忆过程:使用记忆/知道范式的调查

...象。场景的空间布局是指对象和地标在空间中定位方式的内部表示(Evans,1980;Spencer 等,1989))。例如,对象形成场景的图像可以包含棕榈树、海滩、背景为大海的躺椅。另一方面,随机放置在空白背景上的棕榈树、牙刷和电...

2014-11-09 #经典句子

了解了语言的内部结构 你就了解了英语的运行机制 更了解了世界

了解了语言的内部结构 你就了解了英语的运行机制 更了解了世界

...我们互相之间按照一定的原则来进行交流沟通。从语言的内部结构看,语言是抽象的符号系统。零度,指的是语言的语音系统、语义系统、词汇系统、语法系统的规则,这是使用某种语言系统的全体成员所共有的必须遵守的抽象...

2023-05-29 #经典句子

八大行星的矿物探秘:探索太阳系的宝藏

八大行星的矿物探秘:探索太阳系的宝藏

...物其主要由石质和铁质构成,密度较高金星:关于金星的内部结构,还没有直接的资料,从理论推算得出,金星的内部结构和地球相似,有一个半径约3,100公里的铁-镍核,中间一层是主要由硅、氧、铁、镁等的化合物组成的“幔...

2024-01-24 #大杂绘

道教文化的重地白云观 庄严肃穆的灵官殿

道教文化的重地白云观 庄严肃穆的灵官殿

...整个道观坐北朝南,背靠清幽寂静的山林,想要到达景区内部,必须先要攀爬一段山路。沿着山间小路而上,就能够看到牌楼和山门。寺庙的主体建筑和大殿都设立在中心区域,左右两侧是配殿和偏殿。道观中的道路分为左、中...

2018-09-06 #经典句子

滕王阁 一座感受中华古文化内涵的阁楼

滕王阁 一座感受中华古文化内涵的阁楼

...所看到的假象。滕王阁之奇除了建造高之外,还在与它的内部构造,这座楼阁虽然看上去整体的构造比较简约,但是也不失其大方。此外,滕王阁的内部占地面积虽然比较大,但是它的内部功能分区却非常的明确,每一层都有它...

2010-12-21 #经典句子