两种执行引擎解释器
如何理解Java是半编译半解释型语言
- javac编译,java运行
- 字节码解释器解释执行,模板解释器编译执行
字节码解释器
- 解释执行,每行java字节码先解析为c++,再解析为硬编码,一行一行执行
- 效率偏低,小项目可以使用
CASE(_new): {
u2 index = Bytes::get_Java_u2(pc+1);
ConstantPool* constants = istate->method()->constants();
if (!constants->tag_at(index).is_unresolved_klass()) {
// Make sure klass is initialized and doesn't have a finalizer
Klass* entry = constants->slot_at(index).get_klass();
assert(entry->is_klass(), "Should be resolved klass");
Klass* k_entry = (Klass*) entry;
assert(k_entry->oop_is_instance(), "Should be InstanceKlass");
InstanceKlass* ik = (InstanceKlass*) k_entry;
if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
……
模板解释器
- 通过即时编译先将字节码对应的代码编译生成硬编码,直接执行硬编码就可,效率高
- 执行的硬编码就是即时编译器给编译的
void TemplateTable::_new() {
transition(vtos, atos);
Label slow_case;
Label done;
Label initialize_header;
Label initialize_object; // including clearing the fields
Register RallocatedObject = Otos_i;
Register RinstanceKlass = O1;
Register Roffset = O3;
Register Rscratch = O4;
__ get_2_byte_integer_at_bcp(1, Rscratch, Roffset, InterpreterMacroAssembler::Unsigned);
__ get_cpool_and_tags(Rscratch, G3_scratch);
// make sure the class we're about to instantiate has been resolved
// This is done before loading InstanceKlass to be consistent with the order
// how Constant Pool is updated (see ConstantPool::klass_at_put)
__ add(G3_scratch, Array<u1>::base_offset_in_bytes(), G3_scratch);
__ ldub(G3_scratch, Roffset, G3_scratch);
__ cmp(G3_scratch, JVM_CONSTANT_Class);
__ br(Assembler::notEqual, false, Assembler::pn, slow_case);
……
即时编译器
C1
- c1编译器是client模式下的即时编译器
- 需要收集的数据较少,即达到触发即时编译的条件较宽松
- 自带的编译优化优化的点较少:基本运算在编译的时候运算掉了
- 编译时较C2,没那么耗CPU,带来的结果是编译后生成的代码执行效率较C2低
C2
- c2编译器是server模式下的即时编译器
- 触发的条件比较严格,一般来说,程序运行了一段时间以后才会触发
- 需要收集的数据较多
- 编译时很耗CPU
- 编译优化的点较多
- 编译生成的代码执行效率较C1更高
混合编译
- 程序运行初期因为产生的数据较少,这时候执行C1编译,程序执行一段时间后,收集到足够的数据,执行C2编译器
即时编译触发条件
- 目前的64bit机器上只有server模式。执行引擎是server模式启动的JVM中的执行引擎
- 触发即时编译的最小单位是代码段,最大单位是方法
- Client 编译器模式下,N 默认的值 1500 – C1
- Server 编译器模式下,N 默认的值则是 10000 – C2
热度衰减
- 即时编译器会记录触发条件值N,
- 如果一段时间N没有变化,就会按照2倍速度衰减
- 就会导致后面在编译的时候N值降低,需要编译更多
热点代码缓存区
- 保存在方法区的,也是调优需要调的地方 InitialCodeCacheSize/ReservedCodeCacheSize
- server 编译器模式下代码缓存大小则起始于 2496KB
- client 编译器模式下代码缓存大小起始于 160KB
- 查看 : java -XX:+PrintFlagsFinal -version | grep InitialCodeCacheSize
集群扩容热点代码缓存带来的问题
- 热机情况下缓存区缓存数据比较完善,处理代码效率相对较高,
- 集群扩容切入冷机的时候,冷机没有热点缓存,不能直接按照集群中热机的流量进行切入
- 热点缓存过程中逐步提升流量
逃逸分析
- 默认开启
- 逃逸:逃出当前作用域
基于逃逸分析的三种优化
- 栈上分配
○ 不会逃逸出当前栈帧(方法),如成员变量,直接分配到虚拟机栈上 - 标量替换
○ 标量:不可再分,java中的基本数据类型就是标量
○ 聚合量:可再分,对象
○ jvm逃逸分析发现使用的对象的某些变量没有逃逸情况,并且是常量,就会在编译的时候直接用常量替换这个对象.属性的获取值方式 - 锁消除
public void test(){
//new Object没有逃逸,jvm经过逃逸分析认为中国锁是无效的,编译的时候就会去掉这个锁,相当于无锁
synchronized (new Object()){
System.out.println("hello");
}
}
三种运行模式
JIT为什么能提升性能呢?原因是运行期的热点代码编译与缓存
JVM中有两种即时编译器,就诞生了三种运行模式
- -Xint:纯字节码解释器模式 效率最低
- -Xcomp:纯模板解释器模式
- -Xmixed:字节码解释器+模板解释器模式(默认)