字节码是如何运行的?
首先我们先探讨一下字节码是如何运行的,众所周知,java有两种运行模式,一种叫解释执行,一种叫编译执行:
- 解释执行:由编译器一行一行翻译执行
- 编译执行:把字节码翻译成机器码,直接执行机器码
下面我们来对比一下这两种执行模式:
- 解释执行:
- 编译执行:
- 运行效率会高很多,一般认为比解释执行快一个数量级
- 带来了额外开销
那么怎样去查询Java是解释执行还是编译执行的呢? 很简单,执行 java -version 就可以了 我们可以看到有一个 mixed mode 表示混合模式,表示部分代码解释执行,部分代码编译执行。 你可以使用-Xint :把JVM的执行模式设置为解释执行模式 也可以使用 -Xcomp :JVM优先以编译模式运行,不能编译的,以解释执行模式运行。 也可以使用-Xmixed :让JVM以混个模式运行,默认情况下就是混合模式。
JVM如何优化解释器?
在一般情况下
- 一开始由解释器解释执行
- 当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会认为这些代码是“热点代码”。为了提高热点代码的执行效率,会用即时编译器(也就是JIT)把这些热点代码编译成本地平台相关的机器码,并进行各层次的优化。
就目前来说,Hostpot虚拟机内置了两个编译器,也就是C1编译器和C2编译器:
- C1编译器:
- 是一个简单快速的编译器
- 主要关注局部性的优化
- 适用于执行时间比较短或对启动性能有要求的程序。例如,GUI应用对界面启动速度就有一定的要求。
- 也被称为Client Complier
- C2编译器:
- 是为长期运行的服务器端应用程序做性能调优的编译器
- 适用于执行时间较长或对峰值性能有要求的程序
- 也被称为是Server Complier
各层次优化指的是哪些优化?
从JDK7开始,正式引入了分层编译的概念,可以细分为五种编译级别
- 0:解释执行
- 1:简单C1编译:会用C1编译器进行简单优化,不开启profiling(JVM性能监控)
- 2:受限的C1编译:仅执行带方法调用次数以及循环回边执行次数Profiling的C1编译
- 3:完全C1编译:会执行带有所有Profiling的C1代码
- 4:C2编译:使用C2编译器进行优化,该级别会启用一些编译耗时比较长的优化,一些情况下会根据性能监控信息进行一些非常激进的性能优化
一般来说级别越高,应用启动越慢,优化的开销越高,峰值性能也越高
分层编译-JVM参数配置
默认情况下,JDK8是开启分层编译的
- 只想开启C2:-XX:-TieredCompilation(禁用中间编译层(123层))
- 只想开启C1:-XX:+TieredCompilation -XX:TieredStopAtLevel=1
如何找到热点代码?
前面提到过,编译执行主要是针对热点代码的,那么如何找到这些热点代码呢? 就目前来说,业界对于找到热点代码的思路有两种
- 基于采样的热点探测
大致思路是:周期检查各个线程的栈顶,如果某个方法经常出现在栈顶,那么就会认为这个方法是热点方法。 - 基于计数器的热点探测
大致思路是为每个方法或者代码块建立计数器, 统计执行的次数,如果超过一定阈值,那么就会认为它是热点方法。
Hotspot虚拟机是采用的基于计数器的热点探测。
Hotspot内置的两类计数器
- 方法调用计数器(Invocation Counter)
- 用于统计方法被调用的次数,在不开启分层编译的情况下,在C1编译器下的默认阈值是1500次,在C2模式下是10000次。也可用-XX:CompileThreshold=X执行阈值
- 回边计数器(Back Edge Counter)
- 用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为"回边"(Back Edge)。在不开启分层编译的情况下,C1编译器下的默认阈值13995,C2默认为10700,可使用-XX:OnStackReplacePercentage=X指定阈值
- 建立回边计数器的主要目的是为了触发OSR(OnStackReplacement)编译
当开启分层编译,JVM会根据当前待编译的方法数以及编译线程数来动态调整阈值,-XX: CompileThreshold、-XX:OnStackReplacePercentage都会失效
|