vlambda博客
学习文章列表

传统JIT和java9新特性AOT理解

点击👆蓝色“ 深入原理”,关注并“设为星标

技术干货,第一时间推送



Java变慢的原因

1.  除了少量基本类型用栈存储外,所有对象都使用堆存储。堆的性能低于栈。

2.  很多强制类型转换(cast),耗用内存大。java运行时对类型检测,如果类型不正确会抛出ClassCastException异常。

3.  自动垃圾回收机制要耗用不少内存。


JRE带来的跨平台性

传统JIT和java9新特性AOT理解

 Java 平台程序表示的一个重要部分是字节码序列,它描述了 Java 类中每个方法所执行的操作。字节码使用一个理论上无限大的操作数堆栈来描述计算。这个基于堆栈的程序表示提供了平台无关性,因为它不依赖任何特定本地平台的 CPU 中可用寄存器的数目。可在操作数堆栈上执行的操作的定义都独立于所有本地处理器的指令集。Java 虚拟机(JVM)规范定义了这些字节码的执行(参见 参考资料)。执行 Java 程序时,用于任何特定本地平台的任何 JRE 都必须遵守 JVM 规范中列出的规则。

本地编译本质上是特定于平台的。那么 Java 平台如何在不牺牲平台无关性的情况下实现本地编译的性能?答案就是使用 JIT 编译器进行动态编译,这种方法已经使用了十年。



JIT(动态编译)和AOT(静态编译)

传统JIT和java9新特性AOT理解

Java8传统的JIT执行流程:

一个java文件的执行流程:

A. 但此编译器与通常说的javac那个编译器不同,它其实是将字节码编译为硬件可执行的机器码的。

B. 源代码经javac编译成字节码,class文件,这阶段的优化是指程序编码方面的;许多Java语法特性(泛型、内部类等等),是靠前端编译器实现的,而不是依赖虚拟机;(前端编译:也即把满足Java语言规范的程序转化为满足JVM规范所要求格式的功能)

C. 编译成的Class文件可以直接给JVM解释器解释执行,省去编译时间,加快启动速度;程序字节码经过JIT环境变量进行判断,是否属于“热点代码”(多次调用的方法,或循环等)

    如是,走JIT编译为具体硬件处理器(如sparc、intel)机器码通过Java虚拟机(JVM)内置的即时编译器(Just In Time Compiler,JIT编译器);在运行时把Class文件字节码编译成本地机器码的过程;

优点:
通过在运行时收集监控信息,把"热点代码"(Hot Spot Code)编译成与本地平台相关的机器码,并进行各种层次的优化;可以大大提高执行效率;缺点:
收集监控信息影响程序运行;编译过程占用程序运行时间(如使得启动速度变慢);编译机器码占用内存;

    如否,则直接由解释器解释执行


D.  操作系统及类库调用

E. 硬件

以上实际上是JVM的“混合模式”对java程序的执行方式。

 

以上整个流程就属于JIT动态编译的过程:

补充:

       由于动态编译技术的多项改进,在很多应用程序中,现代的 JIT 编译器可以产生与 C 或 C++ 静态编译相当的应用程序性能。但是,仍然有很多软件开发人员认为 —— 基于经验或者传闻 —— 动态编译可能严重干扰程序操作,因为编译器必须与应用程序共享 CPU。一些开发人员强烈呼吁对 Java 代码进行静态编译,并且坚信那样可以解决性能问题。对于某些应用程序和执行环境而言,这种观点是正确的,静态编译可以极大地提高 Java 性能,或者说它是惟一的实用选择。但是,静态地编译 Java 应用程序在获得高性能的同时也带来了很多复杂性。一般的 Java 开发人员可能并没有充分地感受到 JIT 动态编译器的优点。


Java8为什么采用混合模式:

 对于解释执行,不经过jit直接由解释器解释执行所有字节码,执行效率不高。而编译执行不加筛选的将全部代码进行编译机器码不论其执行频率是否有编译价值,在程序响应时间的限制下,编译器没法采用编译耗时较高的优化技术(因为JIT的编译是首次运行或启动的时候进行的!),所以,在纯编译执行模式下的java程序执行效率跟C/C++也是具有较大差距的。

因此,新版本的jvm默认都是采用混合执行模式。


编译执行和解释执行的区别:

1.   解释程序:

解释程序是高级语言翻译程序的一种,它将源语言(如BASIC和java得到的class字节码文件)书写的源程序作为输入,解释一句后就提交计算机执行一句,并不形成目标程序。就像外语 翻译中的“口译”一样,说一句翻一句,不产生全文的翻译文本。如在终端上打一条命令或语句,解释程序 就立即将此语句解释成一条或几条指令并提交硬件立即执行且将执行结果反映到终端,从终端把命令打入后,就能立即得到计算结果。

总结:源程序输入到计算机后,解释程序将程序逐句翻译,翻译一句执行一句边翻译边执行,不产生目标程序。解释方式是按照远程序中语句的动态顺序,直接地逐句进行分析解释,并立即执行。解释的过程:在程序执行时开始翻译代码为二进制,翻译一句执行一句。

 

2.编译程序:

这是一类很重要的语言处理程序,它把高级语言源程序作为输入,进行翻译转换,产生出机器语言的目标程序,然后再让计算机去执行这个目标程序,得到计算结果。

编译程序工作时,先分析,后综合,从而得到目标程序。所谓分析,是指词法分析和语法分析;所谓综合是指代码优化,存储分配和代码生成。为了完成这些分析综 合任务,编译程序采用对源程序进行多次扫描的办法,每次扫描集中完成一项或几项任务,也有一项任务分散到几次扫描去完成的。

在实际应用中,对于需要经常使用的有大量计算的大型题目,采用执行速度较快的编译型的高级语言较好,虽然编译过程本身较为复杂,但一旦形成目标文件,以后可多次使用。相反,对于小型题目或计算简单不太费机时的题目,则多选用解释型的会话式高级语言,如BASIC,这样可以大大缩短编程及调试的时间。

总结: 编译方式把源程序的执行过程严格地分成两大步:编译和运行。

   1.把源程序全部翻译成目标代码

   2.运行目标代码,获取执行结果。

 

动态编译的缺点:

 但是,动态编译确实具有一些缺点,这些缺点使它在某些情况下算不上一个理想的解决方案。例如,因为识别频繁执行的方法以及编译这些方法需要时间,所以应用程序通常要经历一个准备过程,在这个过程中性能无法达到其最高值。在这个准备过程中出现性能问题有几个原因。首先,大量的初始编译可能直接影响应用程序的启动时间。不仅这些编译延迟了应用程序达到稳定状态的时间(想像Web 服务器经历一个初始阶段后才能够执行实际有用的工作),而且在准备阶段中频繁执行的方法可能对应用程序的稳定状态的性能所起的作用也不大。如果JIT 编译会延迟启动又不能显著改善应用程序的长期性能,则执行这种编译就非常浪费。虽然所有的现代JVM 都执行调优来减轻启动延迟,但是并非在所有情况下都能够完全解决这个问题。

    其次,有些应用程序完全不能忍受动态编译带来的延迟。如 GUI 接口之类交互式应用程序就是这样的例子。在这种情况下,编译活动可能对用户使用造成不利影响,同时又不能显著地改善应用程序的性能。

    最后,用于实时环境并具有严格的任务时限的应用程序可能无法忍受编译的不确定性性能影响或动态编译器本身的内存开销。

    因此,虽然 JIT 编译技术已经能够提供与静态语言性能相当(甚至更好)的性能水平,但是动态编译并不适合于某些应用程序。在这些情况下,Java代码的提前(Ahead-of-time,AOT)编译可能是合适的解决方案。


Java9新特性AOT

传统JIT和java9新特性AOT理解


Java 语言本地编译应该是为传统语言(如 C++ 或 Fortran)而开发的编译技术的一个简单应用。传统的静态语言的模式能够将class文件直接编译成可执行二进制文件。

 可以通过谨慎地使用 AOT 编译代码加快应用程序启动,因为虽然这种代码通常比 JIT 编译代码慢,但是却比解释代码快很多倍。此外,因为加载和绑定AOT 编译代码的时间通常比检测和动态编译一个重要方法的时间少,所以能够在程序执行的早期达到那样的性能。类似地,交互式应用程序可以很快地从本地代码中获益,无需使用引起较差响应能力的动态编译。

设想一个正在进行bug 修复的开发环境:类文件的内容可能随不同的应用程序的执行而变化。此外,Java代码可能在程序执行前根本不存在:比如Java 反射服务通常在运行时生成新类来支持程序的行为。

    缺少关于静态、字段、类和方法的信息意味着严重限制了 Java 编译器中优化框架的大部分功能。

虽然AOT 编译号称动态编译缺点的万能解决方案,但是由于Java 语言本身的动态特性,它也面临着提供本地编译全部潜能的挑战。

牺牲了平台无关性和代码,它们不能利用程序的动态行为!



 JIT(动态编译)和AOT(静态编译)的比较

传统JIT和java9新特性AOT理解

JIT(Just in time:即时编译器、动态编译):

在运行是动态编译,可以只编译那些经常使用的方法(热方法),提高效率;其它的解释。

根据代码结构肯能提高或降低java程序执行速度。(代码结构好提高:某些情况下可比肩C++,不然降低)。

运行时进行本地代码编译而不是在程序运行前编译(C、C++是这种),保证了可移植性

AOT(Ahead of time:提前编译、静态编译):

运行前编译好,缺点是全编译了,不用的也编译了,牺牲了平台无关性和代码质量,因为它们不能利用程序的动态行为,也不具有关于加载的类或类层次结构的信息。

避免 JIT 编译器的运行时性能消耗或内存消耗,或者避免解释程序的早期性能开销。极大提高java代码性能。

 

-Xint, -Xcomp, 和-Xmixed

-Xint和-Xcomp参数和我们的日常工作不是很相关,但是我非常有兴趣通过它来了解下JVM。在解释模式(interpreted mode)下,-Xint标记会强制JVM执行所有的字节码,当然这会降低运行速度,通常低10倍或更多。-Xcomp参数与它(-Xint)正好相反,JVM在第一次使用时会把所有的字节码编译成本地代码,从而带来最大程度的优化。这听起来不错,因为这完全绕开了缓慢的解释器。然而,很多应用在使用-Xcomp也会有一些性能损失,当然这比使用-Xint损失的少,原因是-xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器在运行时创建方法使用文件,然后一步一步的优化每一个方法,有时候会主动的优化应用的行为。这些优化技术,比如,积极的分支预测(optimisticbranch prediction),如果不先分析应用就不能有效的使用。另一方面方法只有证明它们与此相关时才会被编译,也就是,在应用中构建某种热点。被调用很少(甚至只有一次)的方法在解释模式下会继续执行,从而减少编译和优化成本。

注意混合模式也有他自己的参数,-Xmixed。最新版本的HotSpot的默认模式是混合模式,所以我们不需要特别指定这个标记。我们来用对象填充HashMap然后检索它的结果做一个简单的用例。每一个例子,它的运行时间都是很多次运行的平均时间。

 

 参考文章:

https://blog.csdn.net/bfboys/article/details/54171368
https://blog.csdn.net/Hsuxu/article/details/9320699
https://www.baidu.com/link?url=YEo4jCyN3H7WQ6VNdE8jgpZ3-NntcSuqNRDMStUTHP3wkjJWIp8HtvjKlkAwhCOZKIwHRgyIdykGf63hhqKH8_&wd=&eqid=a244deff000695ff000000055af19ee7
https://blog.csdn.net/bfboys/article/details/54171368

https://blog.csdn.net/qq280929090/article/details/79507323



  

-深入原理-  

   知其然并知其所以然    

传统JIT和java9新特性AOT理解