代码编织梦想

image-20201212225428463

执行引擎就是 JVM运行Java程序的一套子系统

Java是半编译半解释型语言

如果面试官问你这个问题,要分成两个角度来讲解

​ 1、javac编译,java运行

​ 2、运行期即时编译+解释执行(字节码解释器解释执行,模板解释器编译执行)

两种解释器的底层实现

JVM中目前来说有两种解释器

具体细节见课堂上操作实战

1、字节码解释器

做的事情是:java字节码->c++代码->硬编码

根据不同的字节码指令,执行不同的操作。比如下面代码

0 new #4 <com/luban/test/Test_4>
3 dup
4 invokespecial #5 <com/luban/test/Test_4.<init>>
7 astore_1
8 goto 8 (0)

执行的伪代码如下


while(true) {
for() {
  char code = 
  switch(code) {
    case NEW:

    break;
    case DUP:

    break;
  }
}
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() ) {
 ……

2、模板解释器

做的事情:java字节码->硬编码

先看一个C程序:模拟的就是模板解释器的底层实现

​ 1、申请一块内存:可读可写可执行

​ JIT在Mac是无法运行的, Mac无法申请可执行的内存块

​ 2、将处理new字节码的硬编码拿过来(硬编码怎么拿到?) lldb 解析可执行文件

​ 3、将处理new字节码的硬编码写入申请的内存

​ 4、申请一个函数指针,用这个函数指针执行这块内存

​ 5、调用的时候,直接通过这个函数指针调用就可以了

image-20201212173500496

有对硬编码的生成规则感兴趣的同学吗?

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);
……    

image-20201212162350308

​ 字节码解释器是解释执行的,是一步一步执行的,比如执行了new,执行硬编码,执行到dup,再执行硬编码。模版解释器前面已经触发了即时编译,把字节码对应的c++代码已经全部编译生成硬编码,所以他直接执行硬编码,所以它的执行效率比字节码解释器高。

三种运行模式

JIT为什么能提升性能呢?原因是运行期的热点代码编译与缓存

JVM中有两种即时编译器,就诞生了三种运行模式

1、-Xint:纯字节码解释器模式

2、-Xcomp:纯模板解释器模式

3、-Xmixed:字节码解释器+模板解释器模式(默认)

java默认的是混合模式,可以通过 java -Xit -version修改对应的模式。

image-20201212204024378

这三种模式,哪种效率最高呢?jvm默认为什么要混合模式

​ 首先要知道,如果一个程序很大,如果纯模版解释器(运行的就是编译好的硬编码)的话,那么运行初期就要编译很长时间,这段时间程序时不运行的。所以如果程序很小的话,那么第二种比较合适。反之采用第三种。

两种即时编译器

​ jdk6以前是没有混合编译的,后来根据两种编译器的使用场景组合起来使用进一步提升性能

1、C1编译器

​ -client模式启动,默认启动的是C1编译器。有哪些特点呢?

  • 需要收集的数据较少,即达到触发即时编译的条件相对C2比较宽松

  • 自带的编译优化的点较少(编译的优化比较浅,基本运算在编译的时候运算掉了,比如final)

  • 编译时较C2,没那么耗CPU,带来的结果是编译后生成的代码执行效率较C2低

2、C2编译器

​ -server模式启动。有哪些特点呢?

  • 触发的条件比较严格,一般来说,程序运行了一段时间以后才会触发。需要收集的数据较多

  • 编译时很耗CPU

  • 编译优化的点较多

  • 编译生成的代码执行效率较C1更高


Server模式和client模式

​ 在64位机上只有Server模式,在32位机上可以java -client -version指定成client模式。

image-20201212205800363

3、混合编译

​ 目前的-server模式启动,已经不是纯粹只使用C2。程序运行初期因为产生的数据较少,这时候执行C1编译,程序执行一段时间后,收集到足够的数据,执行C2编译器

​ Mac中是无法使用JIT的!因为Mac无法申请一块可读可写可执行的内存块


​ 字节码解释器是解释执行的,跟即时编译器无关。模板解释器执行的硬编码就是即时编译器给编译的。即时编译器有C1,C2。

即时编译触发条件

​ 目前的64bit机器上只有server模式。大家现在谈执行引擎,说的都是server模式启动的JVM中的执行引擎

​ 触发即时编译的最小单位是代码段(for,while…),最大单位是方法,比如循环个数N:

Client 编译器模式下,N 默认的值 1500,即达到1500时才触发

Server 编译器模式下,N 默认的值则是 10000

java -client -XX:+PrintFlagsFinal -version | grep CompileThreshold

image-20201212211959451

热度衰减

​ 比如现在一个线程调用某一个方法,已经调用了7000次了,随后很长一段时间又没调用了,这时候就会2倍数往下掉,比如掉到3500,原来的话我需要再执行3001次就可以触发即时编译,但是现在我就需要6501次了。


​ 编译器编译后就生成了硬编码,在JVM中也叫热点代码。

热点代码缓存区

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

​ server 编译器模式下代码缓存大小则起始于 2496KB

​ client 编译器模式下代码缓存大小起始于 160KB

java -XX:+PrintFlagsFinal -version | grep CodeCache

image-20201212212927188

​ 调优的话一般将InitialCodeCacheSize,ReservedCodeCacheSize这俩调成一样大。

即时编译器时如何运行的

​ 其实在JVM中很多系统性的操作,像GC,即时编译都是通过VM_THREAD出发的,可以把它理解成一个队列,当达到出发条件,有线程向这个队列里面推送任务,然后其他线程异步去执行任务。

比如System.gc

​ 1、将这个即时编译任务写入队列QUEUE

​ 2、VM_THREAD从这个队列中读取任务,并运行

执行即时编译的线程有多少,以及如何调优

java -client -XX:+PrintFlagsFinal -version | grep CICompilerCount

-XX:CICompilerCount=N //调优参数 

image-20201212213546646

逃逸分析

理解含义

​ 逃逸分析这个词可要拆成两个词来理解:逃逸、分析;逃逸是一种现象,分析是一种技术手段。

逃逸

​ 外部能访问到就叫逃逸,比如共享变量,返回值,参数。。。

​ 如果对象的作用域不是局部的,也就是逃逸。

​ 逃到哪里呢,可以理解为逃到方法外,线程外。

不逃逸

​ 对象的作用域是局部变量

分析

先想想:为什么要做逃逸分析?

​ 如果对象发生了逃逸,那情况就会变的非常复杂,优化无法实施。基于逃逸分析,JVM开发了三种优化技术(不逃逸的情况)。

​ 以下优化技术只有不逃逸的情况下才能做。

1、栈上分配

​ 逃逸分析默认是开启的,栈上分配就是存在的,对象在虚拟机栈上分配

如何证明栈上分配的存在?

public class StackAlloc {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        long start = System.currentTimeMillis();

        for (int i = 0; i < 1000000; i++) {
            alloc();
        }

        long end = System.currentTimeMillis();

        System.out.println((end - start) + " ms");

        while (true);
    }

    public static void alloc() {
        StackAlloc obj = new StackAlloc();
    }
}

​ 可以这样证明,生成了一个对象100w次,不发生gc的情况下,利用HSDB看堆区是不是有100w个,如果没有,就存在栈上分配。如下图:

image-20201212222024578

可以看到远远不够100w。然后在启动参数加上-XX:+/-DoEscapeAnalysis(-关闭,+开启),进行开启和关闭关闭栈上分配后如下图:

image-20201212222313196

2、标量替换

标量:不可再分,java中的基本数据类型就是标量

聚合量:可再分,对象

看一个例子

public class ScalarReplace {

    public static void main(String[] args) {

    }

    public static void test() {
        Position position = new Position(1, 2, 3);

        System.out.println(position.x);
        System.out.println(position.y);
        System.out.println(position.z);
    }
}

class Position {
    int x;
    int y;
    int z;

    public Position(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

​ Position对象的x,y,z就是一个标量,所以postion就是有效的局部变量此时jvm在做逃逸分析的时候就回把输出代码替换为

System.out.println(1);
System.out.println(2);
System.out.println(3);

​ 这就是标量替换。

3、锁消除

举个例子

public void noEscape1(){
    synchronized (new Object()){
        System.out.println("hello");
    }
}

​ 此时在局部变量中加锁,jvm在做逃逸分析后就会变为这样

public void noEscape1(){
    System.out.println("hello");
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/m0_37838198/article/details/111086663

Java判断一个字符是否可以做Java标识符的起始字符或后续字符-爱代码爱编程

Java判断一个字符是否可以做Java标识符的起始字符或后续字符 public class J_Identifier { public static void main(String[] args) { char c='猫'; if(Character.isJavaIdentifierStart(c))

JDBC对数据库实现CRUD的模拟封装-爱代码爱编程

JDBC对数据库实现CRUD的模拟封装 import com.alibaba.druid.pool.DruidDataSource; import java.lang.reflect.Field; import java.sql.*; import java.util.*; /** * @ Author : xu * @ Date : 2020/

【算法题解】496. 下一个更大元素 - Java多种解法 - 单调栈-爱代码爱编程

1. 题目描述 给定两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。 nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。 示例 1: 输入:

线程池ThreadPoolExecutor使用-爱代码爱编程

ThreadPoolExecutor线程池使用 粗略使用ThreadPoolExecutor ThreadPoolExecutor参数 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,

互联网架构-SpringMVC源码深度解析-027:Servlet与SpringMVC关系-爱代码爱编程

027:Servlet与SpringMVC关系 1 SpringMVC深度源码分析课程介绍2 基于ide构建ServletMaven工程3 如何证明Servlet线程是否安全4 ServletContainerInitializer用法5 基于注解方式构建SpringMVC框架6 使用注解形式启动SpringMVC项目7 SpringServlet

Vue使用v-for与v-if搭配满足条件进行赋值,和v-if三目表达式的使用-爱代码爱编程

Vue使用v-for遍历的时候,通过{{}}进行赋值 {{user.companyName = item.companyName}} <select class="form-control" v-model="user.companyId"> <!-- 遍历所有的公司信息 --> <template v-

作为运维,你需要了解的jvm知识点-爱代码爱编程

作为运维,处理最多的可能就是系统的一些内存报错了,所以整理一下jvm方面的知识点,希望各位读完本篇文章,对jvm能有一个基本的了解   推荐公众号,分享运维知识:龙叔18岁 1·概括 堆是堆(heap),栈是栈(stack),堆栈是栈(这个经常会让新手误会) 1.1·内存结构图 下面几个图是我在网上看到的很棒的jvm内存的概括图

字节码指令(下篇)-爱代码爱编程

上一篇 控制转移指令 程序流程离不开条件控制,为了支持条件跳转,虚拟机提供了大量字节码指令,大体上可以分为 1)比较指令、2)条件跳转指令、3)比较条件跳转指令、4)多条件分支跳转指令、5)无条件跳转指令等。 比较指令 ,比较指令的作用是比较栈顶两个元素的大小,并将比较结果入栈。 ●比较指令有: dcmpg, dcmpl、 fcmpg、fcmpl

扎心了老铁,为什么学那么多还是没进大厂?20个经典又容易疏忽的Java面试题分享-爱代码爱编程

身为一个有梦想的Java程序员,去大厂工作是我们的目标,去大厂面试是一个必要的环节。但是有的人刷了那么多题还是没拿到offer,一次又一次,白白浪费了时间,这次我就准备了一些大家容易忽视的面试题,希望能帮助大家。 点个小赞,好运不断,来个关注,青春常驻 另外本人整理了20年面试题大全,包含spring、并发、数据库、Redis、分布式、

GC日志-爱代码爱编程

9.2. GC日志 1)特别说明 每种垃圾回收器的日志格式是不同的2)实际生产中日志参数设置 在实际生产中要保存的日志格式如下所示,至少需要包含以下内容: -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log 设置日志的保存位置 -XX:+UseGCLogFileRotation 设置日志循环保存 -XX:NumberOfGCL

Spring Boot 打包上传至 Docker 仓库?-爱代码爱编程

重要提示:学习本文之前需要提前了解docker容器相关的知识,了解和熟练运用常用的docker操作命令,如果已经了解了docker容器相关的知识那我们就开搞吧! 以下是完成标题所述功能的大致步骤: 搭建docker镜像仓库修改Spring Boot 配置文件添加docker仓库配置,打包,上传至docker 镜像仓库从自己搭的docker镜像仓库

Integer比较问题和JDK缓存设计-爱代码爱编程

文章目录 Integer比较问题比较对象的值要用equals方法为什么a==1024又是true?总结问题还没结束...一定要用equals吗?JDK缓存机制填坑:为什么IntegerCache类的high不是一开始就赋值了?总结扩展知识:享元设计模式 Integer比较问题 今天旁边搞.net的同事问我,为什么Java中Integer比较得