深入理解jvm-后端编译与优化

本文为读书笔记

1. 即时编译器

热点代码
某个方法或者代码块执行得特别频繁(递归),这些代码称为热点代码
而讲热点代码编译成本地代码的后端编译器就称为即时编译器

围绕文章开头提出的几个问题:

1.为何HotSpot虚拟机要使用解释器与即时编译器并存的架构?

  • 当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即运行。
  • 程序启动后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码,这样可以减少解释器的中间损耗,获得更高的执行效率。当程序运行环境中内存资源限制较大,可以使用解释执行节约内存
  • 解释器与编译器经常是相辅相成地配合工作
    在这里插入图片描述

2.为何HotSpot虚拟机要实现两个(或三个)不同的即时编译器?
主要是为了分层编译
在分层编译的时候,解释器、客户端编译器和服务端编译器就会同时工作,热点代码都可能会被多次编译,用客户端编译器获取更高的编译速度,用服务端编译器来获取更好的编译质量,在解释执行的时候也无须额外承担收集性能监控信息的任务,而在服务端编译器采用高复杂度的优化算法时,客户端编译器可先采用简单优化来为它争取更多的编译时间。

3.程序何时使用解释器执行?何时使用编译器执行?
在这里插入图片描述

4.哪些程序代码会被编译为本地代码?如何编译本地代码?
·被多次调用的方法。·被多次执行的循环体。

编译的目标对象都是整个方法体,而不会是单独的循环体。

第一种情况,由于是依靠方法调用触发的编译,那编译器理所当然地会以整个方法作为编译对象,这种编译也是虚拟机中标准的即时编译方式。

而对于后一种情况,尽管编译动作是由循环体所触发的,热点只是方法的一部分,但编译器依然必须以整个方法作为编译对象,只是执行入口(从方法第几条字节码指令开始执行)会稍有不同,编译时会传入执行入口点字节码序号(Byte Code Index,BCI)。这种编译方式因为编译发生在方法执行的过程中,因此被很形象地称为“栈上替换”(On Stack Replacement,OSR),即方法的栈帧还在栈上,方法就被替换了。

5.如何从外部观察到即时编译器的编译过程和编译结果?

部分运行参数需要FastDebug或SlowDebug优化级别的HotSpot虚拟机才能够支持,Product级别的虚拟机无法使用这部分参数。
即一般人观察不到
自己动手编译,或者非官方编译版本

2. 提前编译器

一条分支是做与传统C、C++编译器类似的,在程序运行之前把程序代码编译成机器码的静态翻译工作;

另外一条分支是把原本即时编译器在运行时要做的编译工作提前做好并保存下来,下次运行到这些代码(譬如公共库代码在被同一台机器其他Java进程使用)时直接把它加载进来使用。

可见安卓爆发的例子就是完美的采用了提前编译

3. 优化技术

编译器优化技术
主要介绍四种:1.最终的技术优化:方法内联 2.最前沿的优化技术:逃逸分析 3.语言无关的经典优化技术:公共子表达式消除 4.语言相关的经典优化技术:数组边界检查消除
方法内联:
方法内联就是把目标方法的代码原封不动地“复制”到发起调用的方法之中,避免真实的方法调用。
对于java方法来说,难点在于很多方法是虚方法,在运行前不知道调用的多态选择,为了解决这个问题,java虚拟机引入了一个名为类型继承关系分析(CHA),编译器会根据不同的情况采用不同的方法:
①如果是非虚的方法,直接进行内联即可。
②如果是虚方法且这个方法在当前程序状态下只有一个目标版本可以选择,可以通过假设进行“守护内联”。因为在后面可能加载到了新的类型会改变CHA结论,所以这种内联属于激进预测性优化,必须预留好“逃生门”。
③如果是虚方法且有多个版本可以选择,将用“内联缓存”的方式来缩减方法调用的开销,可以理解为记录下每次不同版本的方法调用,调用一次后下一次只要判断方法所采取的是什么版本就可以立刻进行内联。
逃逸分析:
这并不是直接优化代码的手段,只是为其他优化措施提供依据的分析技术。
当一个对象在方法里面被定义后,它可能被外部方法所引用,这种被称为方法逃逸,甚至被外部线程访问到,这称为线程逃逸。从不逃逸到方法逃逸再到线程逃逸被称为对象由低到高的不同逃逸程度。
如果一个对象不会逃逸或者逃逸几率极低,则可以做以下的优化:
①栈上分配:让这个对象不在java堆中分配内存,直接在栈上分配内存,对象所占用的内存空间可以随着栈帧出栈而销毁。
②标量替换:一个数据无法分解成更小的数据来表示,那么这就是标量,否则为聚合量。对象是典型的聚合量,如果这个对象不会逃逸,可以将它拆散,根据程序访问情况,将其成员变量恢复为原始类型来访问。
③同步消除:不会逃逸就不会被其他的线程所访问,因此可以将这个变量的同步措施都取消。
公共子表达式消除:
对于已经计算过的变量,在后续调用中可以调用结果,而将变量的表达式删去。
如果这种优化仅仅局限于程序基本块中,可以称为局部公共子表达式消除;优化的范围涵盖了多个基本块,可以称为全局公共子表达式消除。
数据边界检查消除
java是一个动态安全语言,每次访问数据前需要检查上下界,但是每次检查需要浪费掉很多运行时间,因此会采用隐式异常处理,只有发生错误时才会进行检查。

发布了37 篇原创文章 · 获赞 6 · 访问量 4638

猜你喜欢

转载自blog.csdn.net/littlewhitevg/article/details/105567500
今日推荐