java性能优化权威指南---即时编译器(Just-in-time,JIT)

目录

1、JIT编译器概览

2、调优入门:选择编译器类型(client、server或者两者同时使用)

1)、优化启动

2)、优化批处理

3)、优化长时间运行的应用

3、java和JIT编译器版本

4、编译器中级调优

1)、调优代码缓存

2)、编译阈值

3)、检测编译过程

5、高级编译器调优

1)、编译线程

2)、内联(默认开启,-XX:+PrintInling)

3)、逃逸分析(默认开启,-XX:DoEscapeAnalysis)

6、逆优化

7、分层编译级别


四、即时编译器(Just-in-time,JIT    page58---80page)

1、JIT编译器概览

热点编译(频繁执行的代码段)
java文件被编译成中间语言(java字节码)。然后再运行时被jvm进一步编译成汇编语言(在有些情况下,比如只执行一次的代码,可以不进行编译,直接解释器执行???)
字节码编译成汇编语言的过程中有大量的优化,极大地改善了性能;
总结:有些情况下字节码可以不进行编译而直接执行(解释模式运行),有些情况下回编译成汇编然后执行(编译模式运行)

2、调优入门:选择编译器类型(client、server或者两者同时使用)

分层编译:代码先由client编译器编译,随着代码变热,由server编译器重新编译
使用分层编译需要知道server编译器并且保证java命令行包括标志-XX:TieredCompilation,默认是关闭的。在java8中,分层编译默认是开启的。

1)、优化启动

总结:
如果应用的启动时间是首要的性能考量,那client编译器就是最有用的;
分层编译的启动时间可以非常接近于client编译器所获的启动时间;

2)、优化批处理

分层编译器是批处理任务合理的默认选择

3)、优化长时间运行的应用

对于长时间运行的应用来说,应该一直使用server编译器,最好配合分层编译

3、java和JIT编译器版本

不同的java支持不同的编译器;
不同的操作系统和架构所支持的编译器也不相同;
程序不必指定编译器,而是仰仗平台所支持的编译器;

java性能优化权威指南---即时编译器(Just-in-time,JIT)_第1张图片

java性能优化权威指南---即时编译器(Just-in-time,JIT)_第2张图片

 

通过java -version可以查看编译模式

 

4、编译器中级调优


大多数的编译器调优就是在目标机器上选择正确的jvm和编译器,如第2点的三种编译器,除了编译器的选择,还需要额外的调优工作,如下所示:

1)、调优代码缓存

jvm编译代码时,会在代码缓存中保留编译之后的汇编语言指令集。代码缓存的大小固定,所以一旦填满就不能编译更多代码了。很显然,如果代码缓存过小,就可能会有问题,
一些热点被编译了,而其他则没有,最终导致应用的大部分代码都是解释执行(非常慢)。

如果是32位jvm,则进程占用的总内存不能超过4GB。这包括java堆、jvm自身所有代码占用的空间(包括它的本地库和线程栈)、分配发给应用的本地内存(或者NIO库的直接内存),
当然还有代码缓存。所以代码缓存的大小设置是受限的。

代码缓存是一种由最大值的资源,它会影响jvm可运行的编译代码总量;
分层编译很容易达到代码缓存默认配置的上线(特别是在java7中),使用分层编译时,应该监控代码缓存,必要时增加他的大小;

java性能优化权威指南---即时编译器(Just-in-time,JIT)_第3张图片

2)、编译阈值

触发代码编译的最主要因素就是代码执行的频度,一旦达到一定次数,且达到了编译阈值,编译器就可以获得足够的信息编译代码

编译原理:
编译时基于两种jvm计数器的:方法调用计数器和方法中的循环回边计数器。
jvm执行某个java方法时,会检查该方法的两种计数器总数,然后判定该方法是否合适编译。如果合适,该方法就进入编译队列,通常叫标准编译。

当方法和循环执行次数达到某个阈值的时候,就会发生编译;
改变阈值会导致代码提前或推后编译;
由于计数器会随着时间而减少,以至于“温热”的方法可能永远都达不到编译的阈值(特别是对server编译器来说)

3)、检测编译过程

观察代码如何被编译的最好方法是开启PrintCompilation
PrintCompilation开启后所输出的信息可用来确认编译是否和预期一样

5、高级编译器调优

1)、编译线程

后台会根据平台的特点选择相应的编译线程类型和数量
放置在编译队列中的方法的编译会被异步执行;
队列并不是严格按照先后顺序的;
队列的热点方法会在其他方法之前编译,这是编译输出日志中的ID为乱序的另一个原因;

2)、内联(默认开启,-XX:+PrintInling)

内联是编译器所能做的最有利的优化,特别是对属性封装良好的面向对象的代码来说;
几乎用不着调节内联参数,且提倡这样做的建议往往忽略了常规内联和频繁调用内联之间的关系。当考察内联效应时,确保考虑这两种情况;

3)、逃逸分析(默认开启,-XX:DoEscapeAnalysis)

逃逸分析是编译器能做的最复杂的优化。此类优化常常会导致微基准测试失败;
逃逸分析常常会给不正确的同步代码引入“bug”;

6、逆优化

逆优化意味着编译器不得不“撤销”之前的某些编译,结果是应用的性能降低---至少是直到编译器重新编译相应的代码为止。
有两种逆优化的情形:
代码状态分别为“made not entrant”(代码被丢弃);
和“made zombie” (产生僵尸代码)时;

1)、代码被丢弃
2)、逆优化僵尸代码

7、分层编译级别

一共五种执行级别
0解释代码
1简单C1编译代码
2受限的C1编译代码
3完全C1编译代码
4C2编译代码

总结:
分层编译可以在两种编译器和五种级别之间进行;
不建议人为更改级别,仅仅是辅助解释编译日志的输出;

 

 

你可能感兴趣的:(读书笔记)