Java为何需要平衡方法调用与内联
作者:干货满满张哈希 发布时间:2023-01-02 20:37:22
在 Java 中,方法调用一般通过 Virtual Call 还有 Classic Call。
Classic Call 就是直接指向方法的地址,需要一次寻址到方法的地址,比直接执行代码慢。
Virtual Call 需要通过 VMT(Virtual Method Table)。这个VMT存储的是该class对象中所有的Virtual Method,程序运行的时候首先加载实例对象,然后通过实例对象找到VMT,通过VMT再找到对应的方法地址,再执行代码。所以比 Classic Call 更慢。
Java 中除了 static 方法,private 方法以及构造器是 Classic Call 之外,基本都是 Virtual Call。
为了优化,JVM 运行时,JVM使用混合模式来从字节码转换成机器可以运行的机器码,混合模式包括解释器和JIT:
解释器工作机制:
在编译时,主要是将java源代码文件编译为java统一的字节码,但是编译成的字节码并不能直接运行,而是通过JVM读取运行。JVM中的解释器就是将.class文件一行一行翻译之后再运行,翻译就是转换成当前机器可以运行的机器码,它不会一次性把整个文件都翻译过来,而是翻译一句,执行一句,再翻译,再执行,所以解释器的程序运行起来会比较慢,每次都要解释之后再执行。所以,有些时候,我们想是否可以把解释之后的内容缓存起来,这样不就可以直接运行了?但是,如果每段代码都要缓存起来,例如仅仅执行一次的代码也缓存起来,这样太浪费内存了。所以,引入一个新的运行时编译器,JIT来解决这些问题,加速热点代码的执行。
JIT运行时编译器工作机制:
JIT针对热点代码,进行编译与深度优化,优化后的机器码会被缓存起来,存入CodeCache(代码高速缓存)中。对于非热点代码,例如只运行一次的代码(类构造器等等),直接解释执行,更加快速。JIT不仅花更多时间去编译优化,而且还多耗费了很多内存。字节码转换为可执行的机器码,大小会大很多很多倍。这也是为啥,解释器每次都要翻译并且执行,JIT只针对热点代码进行编译优化的原因。JIT编译器执行的一些常见优化操作包括数据分析,从堆栈操作到寄存器操作的转换,通过寄存器分配减少内存访问,消除常见子表达式等。JIT编译器进行的优化程度越高,在执行阶段花费的时间越多。因此,JIT编译器无法承担所有静态编译器所做的优化,这不仅是因为增加了执行时间的开销,而且还因为它只对程序进行了限制。这也就解释了为什么有些JVM会选择不总是做JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。
JIT其中一项很重要的优化就是内联: 内联是将较小方法的树合并或“内联”到其调用者的树中的过程。这样可以加速频繁执行的方法调用。不同分层优化阶段,使用的算法不同。主要包括:
Trivial方法内联
调用图内联
尾部递归消除
虚拟调用优化
这样省略了 calling method。但是,如果将所有方法都内联的话,编译出来的机器码会很大很大,内存占用会急剧增高,效率低下。所以,需要 JIT 把握好这个优化的度
总结起来就是:JIT 是即时优化并编译代码,优化代码包括内联,编译后的代码保存在内存中,也就是代码高速缓存,编译后的代码是很大的,所以不能所有代码都编译,需要是热点代码。并且,内联也会将这个方法变得更大。代码高速缓存也是需要清理的,代码高速缓存占用过高,也会增加清理概率,因为你可能几个方法都是高频执行,但是编译之后占用过大导致超过代码高速缓存限制,那么会发生代码高速缓存清理,就是代码缓存中的编译代码一直在换。清理代码高速缓存,会让所有线程进入 Safepoint,然后才能清理,也就是 stop the world。内联过多,方法变大,这种清理频率也会变大。
来源:https://juejin.cn/post/6921483866372833294
猜你喜欢
- 本文实例讲述了Java使用Jdbc连接Oracle执行简单查询操作。分享给大家供大家参考,具体如下:Java Jdbc 连接 Oracle
- Java实现简单台球桌问题,供大家参考,具体内容如下需求:使小球可以在桌面上移动,移动到桌面边缘将被弹回,显示小区的移动素材:小球照片桌球照
- 原因分析使用ajax从前台页面传输数据到后台controller控制器的时候,出现中文乱码其实乱码问题出现的原因,就是由于默认的tomcat
- 一、结构型模式结构型模式有什么好处?从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题二、适配器模式USB网线转换器三、
- JSONObject 与 JSONArray区别JSONObject:{"area": "武汉",&
- Handler、Message、Loopler、MessageQueen首先看一下我们平常使用Handler的一个最常见用法。Handler
- 写在前面之前想尝试把JWT和Shiro结合到一起,但是在网上查了些博客,也没太有看懂,所以就自己重新研究了一下Shiro的工作机制,然后自己
- 概述Kryo 是一个快速序列化/反序列化工具,依赖于字节码生成机制(底层使用了 ASM 库),因此在序列化速度上有一定的优势,但正因如此,其
- 第一种给容器中的组件加上@ConfigurationProperties注解即可测试:@Component@ConfigurationPro
- 最近都在忙着写一个网站项目,今天做一个分页功能的时候,遇到了分页效果实现不了的问题,查了好久的资料,后来终于是成功解决啦,记录一下1.在po
- 微服务编排框架起始原因 是 我们公司 分布式事务 使用的是 seate 分布式事务框架,现在只在一些小部分使用,因为考虑到seate 对性能
- 1.创建项目修改依赖版本2.创建配置文件package com.huanmingjie.elasticsearch.config;impor
- 类加载器类加载器作用负责将.class文件(存储的物理文件)加载到内存中类加载器过程加载:通过全类名获取这个类准备用流传输,加载进内存,加载
- 一个Java程序的执行要经过编译和执行(解释)这两个步骤,同时Java又是面向对象的编程语言。当子类和父类存在同一个方法,子类重写了父类的方
- 1. 新建TestServlet类package com.yanek.test;import java.io.IOException;imp
- @Conditional的使用@Conditional可以根据条件来判断是否注入某些Bean。package com.morris.spri
- java中的JsonSerializer用法背景今天在写项目的时候遇到一个问题,项目中有个类中有创建时间和更新时间两个属性,但是单位在后端用
- 前言Mybatis作为一个应用广泛的优秀的ORM框架,已经成了JavaWeb世界近乎标配的部分,这个框架具有强大的灵活性,在四大组件(Exe
- 目录Java 中线程池创建的几种方式🐱🏍Executors 工厂方法创建👏 new ThreadPoolExecutor() 自
- sqlite是啥?1、一种轻型数据库2、关系型数据库3、占用资源很低,几百K内存,适合嵌入式设备4、支持windows、linux、unix