【JVM深入理解系列2】执行引擎

执行引擎

JVM运行Java程序的一套子系统

解释器

模板解释器和字节码解释器都是JVM执行引擎中的一种实现方式,用于解释执行Java字节码指令。

字节码解释器

Java字节码-> C++代码->硬件编码

字节码解释器是JVM默认的执行引擎,其将Java字节码指令解释为对应的本地机器指令,然后交给CPU执行。
在解释执行时,字节码解释器需要不停的解析和执行指令,效率相对较低。

模板解释器

Java字节码->硬件编码

模板解释器则是使用预编译的机器码来替代每次执行时的解释,从而提高执行效率。
模板解释器的基本思想是将频繁使用的Java方法编译成本地机器码并缓存下来,
以后每次执行这个方法时直接调用缓存中的机器码,避免了解释执行带来的开销。

底层实现过程

  1. 申请一块内存:可读可写可执行。
  2. 将处理new字节码的硬编码拿过来
  3. 将处理new字节码的硬编码写入申请的内存。
  4. 申请一个函数指针,用这个函数指针执行这块内存。
  5. 调用的时候,直接通过这个指针调用。

三种运行模式

  1. -Xint
  2. -Xcomp
  3. -Xmixed
运行模式 特点 优点 缺点
客户端模式 针对交互式应用程序的默认模式,强调的是优秀的用户交互和较短的启动时间。 优秀的用户交互,较短的启动时间。 较低的性能,较高的内存消耗
服务器模式 针对长时间运行的服务器应用程序的默认模式,强调的是程序长时间稳定运行期间的高性能和稳定性。 较高的性能和稳定性。 较慢的启动时间
混合模式 混合客户端和服务器模式,根据应用程序的实际运行情况进行动态切换。 结合了客户端和服务器模式的优点,可以根据应用程序的实际情况进行动态切换。 初始启动时间较慢,切换时有性能损失。

编译器

正常的C++函数生成的硬编码都有堆栈操作。

  • 字节码解释器
    解释执行的
    和编译器没关系

  • 模板解释器
    执行的硬编码是即时编译器编译的。

JIT

即时编译器

  • C1
    c1编译器在client模式下的即时编译器
  1. 比C2搜集的数据少。触发宽松。
  2. 编译优化比较浅。
  3. C1编译器生成的代码执行效率低。
  • C2
    C2编译器是server模式下的即时编译器
  1. 触发条件比较严格,程序运行一段时间后才执行。
  2. 优化比较深。
  3. 编译生成的代码比C1效率高

混合编译器

  • 即时编译触发条件
    即时编译的最小单位是代码块。
  1. 方法被多次调用:当一个方法被调用多次之后,JIT编译器会根据统计信息来判断是否对该方法进行编译。
  2. 热点代码:如果某一段代码被多次执行,且执行时间占据了总执行时间的一定比例,那么JIT编译器会将这段代码编译成本地代码,以提高其执行效率。
  3. 被频繁调用的循环体:如果某个循环体被多次执行,且执行时间占据了总执行时间的一定比例,那么JIT编译器会将该循环体编译成本地代码,以提高其执行效率。

PS:反射调用:反射调用是一种运行时动态调用的方式,它会对JIT编译器造成困难,因为编译器难以在编译时确定要调用的方法和对象类型。
因此,在进行反射调用时,JIT编译器可能不会进行编译,以避免出现错误。

  • N热度:
    生成热点代码
    client,N默认是1500
    server,N默认是10000

  • 热度衰减: new 7000
    一段时间没执行后会2倍速递减 -> 3500

JIT编译的经典故障

业务增长,加节点。热机切冷机,冷机还未进行JIT编译,效率不及热机。导致请求延迟。

冷机:刚运行不久
热机:运行了一段时间
分析:
加节点时:相同配置节点加入,负载均衡平摊压力。 热机有热点代码缓存了,能承载的并发更大。
由于热机能承受的并发大于冷机。冷机一边在增加流量一遍在即时编译。性能就降低。在双重因素的
影响下,冷机反应速度降低。造成请求超时。

问题:热点代码缓存在哪里?
热点代码缓存在方法区: CodeCache(调优方向之一)
可以配置热点代码的大小,server模式是: 2496 2M,client模式下160k

即时编译是如何运行的

JIT编译过程一般分为三个阶段:

  1. 解释执行阶段:程序刚开始执行时,字节码解释器会逐行解释执行字节码指令,同时记录下被频繁执行的热点代码。
  2. 编译准备阶段:当某段代码被执行的次数超过一定的阈值时,JIT编译器会将该段代码标记为“热点代码”,并对其进行编译准备工作,包括类型推断、方法内联、数据流分析等。
  3. 编译执行阶段:JIT编译器将编译准备好的代码转换成本地机器代码,并执行该代码。此时程序的执行效率会有明显的提升。
  • VM_THREAD
    VM_THREAD 是 HotSpot 虚拟机中用来表示一个 Java 线程的数据结构。在 HotSpot 虚拟机内部,VM_THREAD
    类型的变量主要被用来存储 Java 线程的状
    态和相关信息,例如线程 ID、当前 Java 方法栈帧的指针、线程调用栈的栈帧信息、局部变量、操作数栈、返回值等等。

热点数据触发后,队列存放既时编译任务,当触发即时编译时会将编译任务放到及时编译队列里。
其顺序:
1、 触发既时编译任务入队列。
2、 VM_THREAD执行队列任务。

JIT调优

  1. 即时编译的线程有多少,如何调优?
  2. 硬编码,热点代码 热点代码存在哪里?热点代码缓冲区在哪里?
    热点代码缓冲区,在方法区。
  • 逃逸分析

逃逸分析(Escape Analysis)是Java虚拟机(JVM)对代码进行分析的一种技术,
旨在确定对象的生命周期是否跨越了某些线程或方法的边界。
在这种技术中,JVM会尝试分析对象的创建和使用情况,以判断对象是否逃逸出了方法的作用域,
进而决定是否可以对对象进行栈上分配等优化。

逃逸分析的作用域是非局部变量。判断对象是否是局部变量,局部变量是非逃逸对象。非局部变量,逃逸,判断是否在
方法外,县城外被引用。如果没有则判断是逃逸。

  • 基于逃逸技术,JVM开发了三种优化技术。
  1. 栈上分配
    逃逸分析还可以判断出哪些对象的引用不会逃逸到方法的外部,这些对象可以直接分配在栈上。这个过程叫做栈上分配。通过栈上分配,可以进一步减少对堆的使用,减轻垃圾回收的压力,提高程序的性能。
  2. 标量替换
    逃逸分析首先会将对象拆解成若干个基本数据类型,这些基本数据类型可以直接保存在栈上。这个过程叫做标量替换。通过标量替换,可以将原本存储在堆上的对象的部分或全部字段分配到栈上,从而减少对堆的使用,进而提高程序的性能。
  3. 锁消除
    没竞争的锁,会消除掉。

问题

  1. 如何通过代码测试是栈上分配?
    对象在堆区分配,对象在虚拟机栈上分配。
    工具:HSDB
  2. JDK8的栈上分配存在吗?
    生成一个对象100W次,在栈上是不是有100W个。如果没有,就存在栈上分配,不发生GC的情况下。