JVM学习之执行引擎

什么是执行引擎?

在我理解就是,java编译之后,形成的字节码文件就需要执行引擎执行或解析成硬编码,也就是机器指令。

Java是半编译半解释型语言

1.javac编译,java运行
2.字节码解释器是解释运行的
3.运行期即时编译,编译成硬编码执行

JVM两种解释器

字节码解释器

字节码解释器做的事:字节码->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() ) {
 ……

模板解释器

模板解释器做的事:字节码->硬编码
模板解释器底层实现步骤

  1. 向内存申请一块可读可写可执行的内存 (mac系统不行)
  2. 将处理new字节码的硬编码拿过来(硬编码怎么拿到?) lldb 解析可执行文件
  3. 将硬编码放到申请的内存中
  4. 申请一个函数指针,用这个函数指针执行这块内存
  5. 使用的时候,调用这个函数指针
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);
……    

在这里插入图片描述

三种运行模式

JIT即时编译为什么会这么快?
原因是运行期的热点代码编译与缓存
什么叫热点数据?

一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体都可以被称之为“热点代码”,由于这种编译方式发生在方法的执行过程中,因此也被称之为栈上替换,或简称为OSR (On StackReplacement)编译

什么是热度衰减?

当超过一定的时间限度, 如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用热度衰减

JVM中有两种即时编译器,就诞生了三种运行模式
-Xint:纯字节码解释器模式
-Xcomp:纯模板解释器模式
Xmixed:字节码解释器+模板解释器(默认)

两种即时编译器

  1. c1
    在-client模式下,默认启动c1,特点:
    1、需要收集的数据较少,即达到触发即时编译的条件较宽松
    2、自带的编译优化优化的点较少
    3、编译时较C2,没那么耗CPU,带来的结果是编译后生成的代码执行效率较C2低
  2. c2
    -server模式启动。特点:
    1、收集数据较多
    2、编译很费CPU
    3、编辑的优化点比较多
    4、代码执行效率比较高
  3. 混合编译
    目前的-server模式启动,已经不是纯粹只使用C2。程序运行初期因为产生的数据较少,这时候执行C1编译,程序执行一段时间后,收集到足够的数据,执行C2编译器

c1和c2编译的代码就是给模板解释器执行的!

即时编译触发条件

在client模式下1500次
在server模式下10000次
触发即时编译的最小单位是代码段,最大单位是方法
查看值
java -client -XX:+PrintFlagsFinal -version | grep CompileThreshold

这个阈值可以通过虚拟机参数-XX :CompileThreshold来人为设定

热点代码缓存区

热点代码缓存是保存在方法区的,这块也是调优需要调的地方

server 编译器模式下代码缓存大小则起始于 2496KB
client 编译器模式下代码缓存大小起始于 160KB
查看数据
java -XX:+PrintFlagsFinal -version | grep InitialCodeCacheSize

逃逸分析

什么是逃逸分析?
可以把逃逸和分析拆开
逃逸是指变量逃出方法内,逃到方法外或逃出线程外。
也可以先说什么是不逃逸,肯定不逃逸的是局部变量,那么可以说除了局部变量,其他的就是逃逸的。比如参数,全局变量,返回值等

分析:基于不逃逸的基础上进行优化

public class EscapeTest {

    public static Object globalVariableObject;

    public Object instanceObject;

    public void globalVariableEscape(){
   	    静态变量,外部线程可见,发生逃逸
        globalVariableObject = new Object(); 
    }

    public void instanceObjectEscape(){
    	赋值给堆中实例字段,外部线程可见,发生逃逸
        instanceObject = new Object(); 
    }
    
    public Object returnObjectEscape(){
        返回实例,外部线程可见,发生逃逸
        return new Object();  
    }

    public void noEscape(){
        synchronized (new Object()){
            //仅创建线程可见,对象无逃逸
        }
        仅创建线程可见,对象无逃逸
        Object noEscape = new Object();  
    }
 	public void test(){
       未逃逸
       int a = 0;
       int b = 1;
    }
}

基于逃逸分析诞生的三种优化策略:

栈上分配

栈上分配主要是指在Java程序的执行过程中,在方法体中声明的变量以及创建的对象,将直接从该线程所使用的栈中分配空间。 一般而言,创建对象都是从堆中来分配的,这里是指在栈上来分配空间给新创建的对象。
如何证明?
生成了一个对象100w次,现在看堆区是不是有100w个,不发生gc的情况下,如果没有,就存在栈上分配。
在这里插入图片描述

在这里插入图片描述

标量替换

也就是在栈上把基本数据类型,对象结构不可再分的直接替换掉,而不是去找内存地址
在这里插入图片描述

锁消除

这个就很好理解了。这里发现对象没有逃逸。可能把关键词去掉,也可能是让它没有竞争对手。让同步失效,这样可以支持更大的并发

  public void noEscape(){
        synchronized (new Object()){
            //仅创建线程可见,对象无逃逸
            int a =0;
            a =10;
        }
        仅创建线程可见,对象无逃逸
        Object noEscape = new Object();  
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值