为了解释什么是JIT编译器,我想先定义一个编译器概念。 根据维基百科,编译器是“将源语言转换为另一种计算机语言(目标语言)的计算机程序”。
我们都熟悉静态Java编译器(javac),该程序将人类可读的.java文件编译为可以由JVM解释的字节码– .class文件。 那么,JIT编译什么? 在解释什么是“及时”之后,稍后会给出答案。
根据大多数研究,执行时间的80%用于执行20%的代码。 如果有一种方法可以确定那20%的代码并对其进行优化,那就太好了。 这正是JIT所做的–在运行时,它收集统计信息,找到“热”代码,将其从JVM解释的字节码(存储在.class文件中)编译为由操作系统直接执行的本地代码,并对其进行大量优化。 最小的编译单元是单一方法。 编译和统计信息收集是通过特殊线程与程序执行并行进行的。 在收集统计数据期间,编译器对代码功能进行假设,并且随着时间的流逝试图证明或反证它们。 如果假设不成立,则将对代码进行优化,然后再次重新编译。
之所以选择Sun(Oracle)JVM的名称“ Hotspot”,是因为该虚拟机能够在代码中找到“热点”。
JIT有哪些优化?
让我们仔细看看JIT所做的更多优化。
- 内联方法–而不是在对象实例上调用方法,而是将方法复制到调用者代码。 热方法应尽可能靠近调用者,以防止任何开销。
- 如果无法从其他线程访问监视器,则消除锁定
- 用直接方法调用替换接口,以实现仅实现一次的方法,以消除对虚拟函数的调用
- 在同一对象上连接相邻的
synchronized
块 - 消除无效代码
- 对非易失
volatile
变量的直接存储器写操作 - 删除预检查NullPointerException和IndexOutOfBoundsException
- 等等
Java VM调用Java方法时,它将使用已加载的类对象的方法块中指定的调用者方法。 Java VM具有多个调用程序方法,例如,如果该方法是同步的或本机方法,则使用不同的调用程序。 JIT编译器使用其自己的调用程序。 Sun生产发行版检查方法访问位的值ACC_MACHINE_COMPILED,以通知解释器该方法的代码已经编译并存储在装入的类中。 JIT编译器将方法块编译为该方法的本机代码,并将其存储在该方法的代码块中。 编译代码后,将设置Sun平台上使用的ACC_MACHINE_COMPILED位。 我们如何知道JIT在程序中正在做什么以及如何对其进行控制?
首先禁用JIT可以使用Djava.compiler = NONE参数。
Hotspot中有两种类型的JIT编译器–一种用于客户端程序,一种用于服务器(VM参数中的-server选项)。 在服务器上运行的程序通常比在客户端上运行的程序享有更多的资源,并且对服务器来说,程序的最高吞吐量通常更为重要。 因此,服务器中的JIT会消耗更多资源,并且收集统计信息会花费更多时间来使统计信息更加准确。 对于客户端程序,方法的静态收集持续进行1500个方法调用,对于服务器15000。这些默认值可以通过– XX:CompileThreshold = XXX VM参数进行更改。
为了找出默认值是否适合您,请尝试启用“ XX:+ PrintCompilation”和“ -XX:-CITime”参数,这些参数将打印JIT统计信息和JIT花费的时间。
基准测试
大多数基准测试表明,JIT代码的运行速度比解释代码快10至20倍。 完成了许多基准测试。 下面给出了其中两个的结果图:
值得一提的是,以JIT模式运行但仍处于“学习模式”的程序比非JITed程序运行慢得多。
准时制的缺点
JIT增加Java程序中不可预测性和复杂性的水平。 它增加了开发人员并不真正理解的另一层。 可能的错误示例-并发中的“关系发生前”。 如果更改对于在单线程中运行的程序是安全的,则JIT可以轻松地对代码重新排序。 为了解决此问题,开发人员使用“同步”字或显式锁定来提示JIT。 增加非堆内存占用量– JIT代码存储在“代码缓存”生成中。
先进的准时制
JIT和垃圾回收。
- 要使GC发生,程序必须达到安全点。 为此,JIT定期在本机代码中插入屈服点。
- 除了扫描堆栈以查找根引用外,还必须扫描寄存器,因为它们可能包含JIT创建的对象
参考: The Art of Java博客中我们的JCG合作伙伴 Art Gourevitch 在Hotspot中提供的即时编译器(JIT) 。
翻译自: https://www.javacodegeeks.com/2012/06/just-in-time-compiler-jit-in-hotspot.html