JVMの静、动态绑定异常捕获JIT即时编译

        在说明静态绑定和动态绑定之前,我们首先要了解在字节码指令的层面,JVM是如何调用方法的:

        例如我有以下的代码,很简单就是在main方法中调用了另一个静态方法

public class MethodTest {public static void main(String[] args) {study();}private static void study() {System.out.println("... study");}
}

        编译成的字节码文件如下:

        关键在于invokestatic 这个字节码指令,它的作用就是调用一个静态方法,与此类似的还有:

  • invokevirtual:用于调用实例方法
  • invokespecial:用于调用私有方法构造方法,通过super关键字调用父类的构造方法
  • invokeinterface:调用接口方法
  • invokedynamic:用于调用动态方法

1、静态绑定

        静态绑定也称为早期绑定(Early Binding)。在编译时就决定了要调用的方法,通常会发生在调用静态方法,私有方法,final方法时

        这三种方法都有一个共同点,那就是在编译期间就能被确定。

        在上面的案例中,我们可以看到,编译成的字节码文件中,调用方法的关键字后跟上了一个#符号引用,它指向常量池中的方法定义:

        静态绑定的情况下,符号引用是在第一次方法被调用时,替换成为直接引用。JVM会从常量池中找到编号为2的项,根据这些信息定位到实际的类和方法,将符号引用替换为直接引用。

        JVM在加载类时并不会立即解析所有符号引用。相反,符号引用的解析通常发生在第一次实际使用这些引用的时候。这种机制称为延迟解析。

        例如案例中study方法的解析过程:

  1. 首先会找到常量池中#2的元素。
  2. 然后会把MethodTest类加载到内存中。
  3. 通过符号引用中的方法名和描述符,定位到study方法。
  4. 将符号引用#2替换成直接指向study方法的引用。

2、动态绑定

        动态绑定也称为后期绑定(Late Binding)或运行时绑定(Runtime Binding)。在运行时决定调用哪个方法。

        最常见的场景是为了支持多态:当一个父类的引用变量引用子类的对象时,调用重写的方法时会在运行时决定调用子类的实现,通常发生在invokevirtualinvokeinterface字节码指令中。

        而虚方法表又是实现多态的一种方式,什么是虚方法表

        在前面的文章中提到过,类在加载阶段JVM会将读取到的字节码信息保存到内存的方法区中,生成一个InstanceKlass对象,InstanceKlass对象 中就包含了虚方法表。


           例如我现在有A一直到G这么多类,每个类的父类都是上一个类。如果G类需要调用A类中的方法,难道会从G一直找到A?答案是否定的。

        每个类中都有一个虚方法表,记录了类中的每个方法以及方法的地址:

        如果子类继承了父类,会先复制一份父类的虚方法表,然后加上自己特有的方法,如果重写了父类中的某个方法,就会在自己类的虚方法表中将父类中被重写方法的地址指向本类。(这就是为什么子类重写了父类的方法,以子类的该方法为准。)

        动态绑定时,字节码指令执行的流程是,首先根据每个对象头中的元数据指针,去找到方法区中的InstanceKlass对象,然后根据虚方法表获得对应方法的地址,最后调用方法。

        下面我们通过一个案例说明一下动态绑定的执行流程:

class Animal {public void makeSound() {System.out.println("Animal sound");}
}class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("Dog barks");}
}public class Test {public static void main(String[] args) {Animal a = new Dog();a.makeSound();  // 动态绑定}
}

        当a.makeSound();执行时,会执行以下的操作:

  1. 编译阶段:a是animal类型的引用,但是指向了Dog实例,使用invokevirtual 字节码指令去调用makeSound方法,但是此时不会去决定调用哪个实例的makeSound方法。并且此时是符号引用
  2. 加载和连接阶段:JVM会加载Animal和Dog类,并进行连接,此时常量池中的符号引用被替换成了直接引用 ,但是还没有动态绑定。
  3. 运行时阶段:当运行到a.makeSound()时,会真正的执行invokevirtual 字节码指令,JVM会发现a的实际类型是Dog实例。会去Dog类的虚方法表查找makeSound方法。因为Dog重写了父类的makeSound方法,makeSound方法实际指向了Dog类。
  4. 执行方法:JVM使用方法表中的直接引用调用Dog类的makeSound方法。

        Animal和Dog的虚方法表:

Animal VTable:
+-----------------+
| makeSound() ->  |  --> 指向 Animal::makeSound
+-----------------+

Dog VTable:
+-----------------+
| makeSound() ->  |  --> 指向 Dog::makeSound
+-----------------+

        虽然在连接阶段中的解析这一步会把符号引用替换成直接引用,但是还没有进行动态分派。需要在运行时根据对象实际的类型查询虚方法表。

3、异常捕获处理

        在之前的文章中,有提到过每个方法都有其异常表,例如有一个方法:

public class ExceptionTest {public static void main(String[] args) {try {int i = 0;}catch (Exception e){int i = 2;}}
}

        它的字节码指令:

0 iconst_0
1 istore_1
2 goto 8 (+6)
5 astore_1
6 iconst_2
7 istore_2
8 return

        对应的异常表:

        这个异常表中的起始PC结束PC,表示捕获异常生效的字节码起始和结束位置。

        跳转PC指的是出现异常并被捕获后,跳转到字节码指令的位置。

        如果有多个catch分支捕获不同的异常呢?

public class ExceptionTest {public static void main(String[] args) {try {int i = 0;}catch (ClassCastException e){int i = 2;}catch (NullPointerException ex){int i= 3;}}
}

        在异常表的层面会从上往下遍历,如果出现的异常与第一个不匹配,就会去查找第二个,第三个...

        Finally代码块的处理,Finally代表无论是否出现异常,最终一定会执行的代码,那么在字节码的层面是如何进行处理的?

public class ExceptionTest {public static void main(String[] args) {try {int i = 0;}catch (ClassCastException e){int i = 2;}finally {int i = 10;}}
}

         首先看一下编译后的异常表:

        会发现除了catch中的ClassCastException,还多了两个any捕获类型,表示捕获所有类型的异常。

        Nr.1的any,其实对应的就是try块中的代码,Nr.2的any,对应的是catch块中的代码。

        实际上是把finally中的逻辑插入到了try和catch代码块中。

4、JIT即时编译

        JIT(Just-In-Time)即时编译器是一种在程序运行时将Java字节码动态编译为机器码的技术,以提高程序的执行效率。

        第一篇中提到过,Java语言支持跨平台特性的实现在于,Java程序在开发完成后会被编译成字节码,然后JVM会将字节码转换为具体平台的机器码进行执行。

        如果有一些代码的执行频率较高,这样的代码会被称之为热点代码,会被JIT即时编译器编译成机器码的同时进行优化,保存在内存中。

        在JVM中,一般有两种即时编译器:

  • C1:适用于需要快速启动时间的应用,如桌面应用。它在做简单优化的同时,能快速完成编译。
  • C2:适用于长时间运行的服务器端应用。它进行更多、更复杂的优化,以获得最佳性能。

        通常情况下C1和C2不会单独工作,而是会协同进行,这就引出了分层编译机制

        4.1、分层编译

        分层编译是JVM中一个重要的优化策略,它结合了C1和C2编译器的优点,既能实现快速启动,又能在长时间运行时提供高效的优化:

  1. Tier 0 - 解释执行:JVM启动时,所有方法最初都是通过解释器执行的。这允许应用程序快速启动,因为解释执行不需要任何编译时间。
  2. Tier 1 - 简单编译(C1 without profiling):当一个方法被调用多次,达到一定的阈值时,JVM会使用C1编译器对其进行简单的编译。这种编译会生成未经复杂优化的机器码,但执行速度比解释执行要快。
  3. Tier 2 - 带性能分析的编译(C1 with profiling):在这个层次上,C1编译器不仅进行编译,还会在生成的机器码中插入性能分析代码(profiling code)。这些性能分析代码会收集运行时数据,例如方法调用频率、分支预测信息和类型分布等。这些数据将用于后续更高级别的优化。
  4. Tier 3 - 更高级的编译(C1 with more profiling):这一层次进一步加强性能分析,同时进行更多的中等优化。
  5. Tier 4 - 高级编译(C2):当方法经过充分的性能分析并被标记为热点方法时,JVM会使用C2编译器对其进行高级编译。C2编译器会利用收集到的性能数据进行深入的优化,包括内联、循环优化和逃逸分析等。

        由此可见在分层编译时,JVM会优先使用C1编译器为C2编译器收集信息,协同C2编译器进行编译。C1和C2一般都是用独立的线程进行处理,线程中存有队列存放需要编译的任务。

        那么C1和C2是如何协同工作的?

  1. 启动阶段:JVM启动时,所有方法通过解释器执行(Tier 0)。这保证了应用程序能够快速启动。
  2. 热点探测:JVM通过计数器机制监控方法的执行频率。当某个方法调用次数达到Tier 1的阈值时,C1编译器介入,对该方法进行简单编译。
  3. 性能分析和优化:在Tier 2和Tier 3层次上,C1编译器插入性能分析代码,收集运行时的性能数据。JVM根据这些数据判断哪些方法应该进一步优化,并在合适的时候使用C2编译器对热点方法进行高级编译。
  4. 持续优化:C2编译器对方法进行高级优化,生成高效的机器码。C2编译的机器码会替换之前C1编译的机器码或解释执行的代码。如果运行时情况发生变化,例如方法的调用频率下降或性能特征改变,JVM可以重新调整编译策略,可能会回退到C1编译,甚至返回解释执行。(称之为取消优化
        4.2、方法内联

        方法内联是指在编译时,将被调用的方法的代码直接插入到调用点,而不是在运行时进行方法调用。这样做可以避免参数传递,接受返回值,创建栈帧等。

        例如我有以下的代码:

public int add(int a, int b) {return a + b;
}public int calculate() {int x = 10;int y = 20;return add(x, y);
}

        在没有进行内联时,调用add方法,会产生一个新的栈帧,并且需要传递参数,得到返回的结果。

        而通过内联,会得到如下的效果:

public int calculate() {int x = 10;int y = 20;return x + y;  // add(x, y) 的内联结果
}

        通常内联后还会进行一次常量折叠(因为案例中x和y的值是在编译时就能确定,不会动态发生变更):

public int calculate() {return 30;  // 常量折叠后的代码
}

        通过上面的简单案例,我们对于什么是方法内联有了一定的认识,下面总结一下方法内联的过程:

  • JIT编译器会根据一定的标准来识别哪些方法适合进行内联。这些标准包括方法的大小、调用频率、编译层次(如C1或C2编译器)和方法的特性(如是否为虚方法)。
  • 一旦确定某个方法可以内联,JIT编译器会将该方法的字节码直接插入到调用点,替代原来的方法调用指令。(在代码层面,就如同上面案例将return add(x, y) 替换成return x + y)
  • 在插入内联方法的代码后,JIT编译器会进行进一步的优化,例如常量折叠、消除无用代码和循环展开等,以最大限度地提高执行效率。

        而需要实现方法内联,也要满足一定的条件,首先是方法的大小:IT编译器通常会设置一个方法大小的阈值,超过这个阈值的方法将不会被内联。其次是调用频率 ,经常被调用的方法更有可能被内联。以及访问修饰符 ,一般被private,final,static修饰的方法更容易被内联,因为它们的调用行为是确定的,不会被子类重写或动态绑定。(这一条不由得让我想到了曾经看到的八股文中final关键字的作用,其中就有一条被final修饰的常量会被虚拟机内联提高效率)

        4.3、逃逸性分析

        逃逸性分析(Escape Analysis)是JVM(Java虚拟机)JIT(Just-In-Time)编译器用来优化内存分配和垃圾回收的重要技术。通过分析对象的动态作用域,JVM可以确定哪些对象不会“逃逸”出其创建的方法线程,从而进行进一步优化,如栈上分配和同步消除。

        根据是否发生逃逸,及逃逸的范围,一般会将对象划分为以下的种类:

        不逃逸:对象完全在创建它的方法内部使用,未被返回或传递给外部。

        对于不逃逸的对象,JVM可以在栈上分配内存,而不是堆上。栈上分配的对象随着方法结束自动销毁,无需垃圾回收。

public class EscapeAnalysisDemo {public static void main(String[] args) {for (int i = 0; i < 10; i++) {Test test = new Test();System.out.println(test);}}
}

        方法逃逸:对象作为返回值或参数传递给外部方法,但不逃逸出创建它的线程。        

public class EscapeAnalysisDemo {public static void main(String[] args) {for (int i = 0; i < 10; i++) {Test test = new Test();method1(test);}}
}

        线程逃逸:对象被其他线程访问,通常通过共享变量或线程间通信传递。

public class EscapeAnalysisDemo {public static void main(String[] args) {Test test = new Test();new Thread(()->{System.out.println(test);},"t1");}
}

        我们再通过另一个案例详细看下逃逸性分析的过程以及JVM做出的优化

public class Example {public static class Point {int x, y;public Point(int x, int y) {this.x = x;this.y = y;}}public void calculate() {Point p = new Point(10, 20);System.out.println(p.x + p.y);}public static void main(String[] args) {Example example = new Example();example.calculate();}
}

        JVM的JIT编译器会分析Point对象的逃逸性:

        Point对象是在calculate()方法中被创建,p.x和p.y也都是发生在calculate()方法中的,Point对象没有传递给其他方法,也不会返回给其他方法。

        4.3.1、栈上分配:

        由于Point对象不逃逸,JVM可以选择在栈上分配Point的内存,而不是在堆上。这样,当 calculate()方法结束时,Point对象的内存会自动释放,无需垃圾回收。

        4.3.2、同步消除

        假设我们在calculate()方法中使用了同步代码块:

    public void calculate() {synchronized(new Point(10, 20)){System.out.println(p.x + p.y);}}

        同步块可以被消除,因为没有其他线程会访问Point对象。

        4.3.3、标量替换

        JVM可以将Point对象分解为两个局部变量X和Y,避免对象的创建

public void calculate() {int x = 10;int y = 20;int sum = x + y;System.out.println(sum);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/850906.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文阅读——MIRNet

项目地址&#xff1a; GitHub - swz30/MIRNet: [ECCV 2020] Learning Enriched Features for Real Image Restoration and Enhancement. SOTA results for image denoising, super-resolution, and image enhancement.GitHub - soumik12345/MIRNet: Tensorflow implementation…

数据库(29)——子查询

概念 SQL语句中嵌套SELECT语句&#xff0c;称为嵌套查询&#xff0c;又称子查询。 SELECT * FROM t1 WHERE column1 (SELECT column1 FROM t2); 子查询外部语句可以是INSERT/UPDATE/DELETE/SELECT的任何一个。 标量子查询 子查询返回的结果是单个值&#xff08;数字&#xff…

电子设计入门教程硬件篇之集成电路IC(二)

前言&#xff1a;本文为手把手教学的电子设计入门教程硬件类的博客&#xff0c;该博客侧重针对电子设计中的硬件电路进行介绍。本篇博客将根据电子设计实战中的情况去详细讲解集成电路IC&#xff0c;这些集成电路IC包括&#xff1a;逻辑门芯片、运算放大器与电子零件。电子设计…

31、matlab卷积运算:卷积运算、二维卷积、N维卷积

1、conv 卷积和多项式乘法 语法 语法1&#xff1a;w conv(u,v) 返回向量 u 和 v 的卷积。 语法2&#xff1a;w conv(u,v,shape) 返回如 shape 指定的卷积的分段。 参数 u,v — 输入向量 shape — 卷积的分段 full (默认) | same | valid full&#xff1a;全卷积 ‘same…

UnityXR Interaction Toolkit 如何使用XRHand手部识别

前言 Unity的XR Interaction Toolkit是一个强大的框架,允许开发者快速构建沉浸式的VR和AR体验。随着虚拟现实技术的发展,手部追踪成为了提升用户交互体验的关键技术之一。 本文将介绍如何在Unity中使用XR Interaction Toolkit实现手部识别功能。 准备工作 在开始之前,请…

统信UOS1070上配置文件管理器默认属性01

原文链接&#xff1a;统信UOS 1070上配置文件管理器默认属性01 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在统信UOS 1070上配置文件管理器默认属性的文章。文件管理器是我们日常操作系统使用中非常重要的工具&#xff0c;了解如何配置其默认属性可以极大地…

apache poi 插入“下一页分节符”并设置下一节纸张横向的一种方法

一、需求描述 我们知道&#xff0c;有时在word中需要同时存在不同的节&#xff0c;部分页面需要竖向、部分页面需要横向。本文就是用java调用apache poi来实现用代码生成上述效果。下图是本文实现的效果&#xff0c;供各位看官查阅&#xff0c;本文以一篇课文为例&#xff0c;…

Linux系统推出VB6开发IDE了?Gambas,Linux脚本编写

第一个Linux程序&#xff0c;加法计算加弹窗对话框,Gambas,linux版的类似VB6的IDE开发环境 一开始想用VB6的Clng函数转成整数&#xff0c;没这函数。 输入3个字母才有智能提示&#xff0c;这点没做好 没有msgbox函数&#xff0c;要用messagebox.warning 如果可以添加函数别名就…

[书生·浦语大模型实战营]——第六节 Lagent AgentLego 智能体应用搭建

1. 概述和前期准备 1.1 Lagent是什么 Lagent 是一个轻量级开源智能体框架&#xff0c;旨在让用户可以高效地构建基于大语言模型的智能体。同时它也提供了一些典型工具以增强大语言模型的能力。 Lagent 目前已经支持了包括 AutoGPT、ReAct 等在内的多个经典智能体范式&#x…

通过双模式对抗提示越狱视觉语言模型

最近&#xff0c;将视觉整合到大型语言模型&#xff08;LLMs&#xff09;中的兴趣显著增加&#xff0c;催生了大型视觉语言模型&#xff08;LVLMs&#xff09;。这些模型结合了视觉和文本信息&#xff0c;如LLaVA和Gemini&#xff0c;已经在包括图像字幕、视觉问题回答和图像检…

论文阅读:All-In-One Image Restoration for Unknown Corruption

发表时间&#xff1a;2022 cvpr 论文地址&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Li_All-in-One_Image_Restoration_for_Unknown_Corruption_CVPR_2022_paper.pdf 项目地址&#xff1a;https://github.com/XLearning-SCU/2022-CVPR-AirNet 代码解读…

c++中, 直接写浮点数, 是float 还是 double?

如果直接一个浮点数, 那么他默认是float还是double呢? 测试用例 #include <iostream> using namespace std;int main() {auto x 0.2;float f 0.2;double d 0.2;cout << "x Size : " << sizeof(x) << " bytes" << endl…

vue28:组件化开发和根组件

简单写个点击事件 <template> <div class"app"><div class"box" click"fn"></div></div> </template><script> export default {//导出当前组件的配置项//里面可以提供 data methods computed wat…

AtCoder Beginner Contest 356 G. Freestyle(凸包+二分)

题目 思路来源 quality代码 题解 对n个泳姿点(ai,bi)建凸包&#xff0c;实际上是一个上凸壳&#xff0c; 对于询问(ci,di)来说&#xff0c;抽象画一下这个图&#xff0c;箭头方向表示询问向量 按x轴排增序&#xff0c;并且使得后面的y不小于前面的y&#xff0c;因为总可以多…

C++ Easyx案例实战:Cookie Maker工作室1.0V

前言 //制作属于自己的工作室&#xff01; 注&#xff1a;运行效果以及下载见Cookie Maker 工作室成立程序。 关于Cookie Maker工作室成立的信息&#xff0c;I am very happy&#xff08;唔……改不过来了&#xff09;。 OKOK&#xff0c;第一次用图形库写程序&#xff08;图形…

在开源处理器架构RISC-V中发现可远程利用的中危漏洞

在RISC-V SonicBOOM处理器设计中发现中度危险的漏洞 最近&#xff0c;西北工业大学的网络空间安全学院胡伟教授团队在RISC-V SonicBOOM处理器设计中发现了一个中度危险的漏洞。这个团队的研究人员发现了一个可远程利用的漏洞&#xff0c;该漏洞存在于开源处理器架构RISC-V中。…

单灯双控开关原理

什么是单灯双控&#xff1f;顾名思义&#xff0c;指的是一个灯具可以通过两个不同的开关或控制器进行控制。 例如客厅的主灯可能会设置成单灯双控&#xff0c;一个开关位于门口&#xff0c;另一个位于房间内的另一侧&#xff0c;这样无论你是从门口进入还是从房间内出来&#x…

java web:springboot mysql开发的一套家政预约上门服务系统源码:家政上门服务系统的运行流程

java web&#xff1a;springboot mysql开发的一套家政预约上门服务系统源码&#xff1a;家政上门服务系统的运行流程 家政上门服务系统的优势 服务质量更稳定&#xff1a;由专业的家政人员提供服务&#xff0c;经过严格的培训和筛选。 价格更透明&#xff1a;采用套餐式收费&…

Word多级标题编号不连续、一级标题用大写数字二级以下用阿拉伯数字

Word多级标题编号不连续 &#xff1a; 一级标题用大写数字二级以下用阿拉伯数字&#xff1a;

墨雨云间王星越雨中情深

墨雨云间&#xff1a;王星越的雨中情深&#xff0c;吻上萧蘅&#xff0c;宿命之恋在烟雨朦胧的《墨雨云间》中&#xff0c;王星越饰演的角色&#xff0c;以其深邃的眼神和细腻的演技&#xff0c;将一段宿命之恋演绎得淋漓尽致。当镜头聚焦于他与阿狸在雨中的那一幕&#xff0c;…