Java编译过程、JIT编译详解、类加载过程

news2024/9/24 4:25:08

文章目录

    • Java编译执行过程
    • 类加载过程
    • 即时编译JIT
    • JIT编译优化中的常见技术
      • 方法内联
      • 逃逸分析
        •    栈上分配
        •    锁消除
    • 小总结

Java编译执行过程

提到编译,可能大多数人想到的就是将**.java编译成***.class文件,但其实Java代码的编译执行是一个非常复杂的过程,将**.java编译成**.class的过程叫做前端编译.

前端编译后的字节码可以由JVM解释器进行解释执行,但是这种执行效率是比较低的,因为它是在软件层面解释执行的,那是否可以有选择性的将运行次数较多的方法直接编译成二进制代码,运行在底层硬件上呢?JIT即时编译器正是承担这个作用.

将字节码编译成机器码的过程叫做运行时编译,它可以将Java字节码变成高度优化的机器代码,从而提高执行效率.

这一篇文章我们就来理一理这整个过程,以及其中的思想方法.
(主要讲解类加载以及之后的流程, 即类加载->运行时编译->机器码)

在这里插入图片描述

类加载过程

类的加载过程主要包括加载、连接、初始化三个部分

加载:

当一个类被创建实例或者被其它对象引用时,虚拟机在没有加载过该类的情况下,会通过类加载器将字节码文件加载到内存中。

不同的实现类由不同的类加载器加载,JDK 中的本地方法类一般由根加载器(Bootstrp loader)加载进来,JDK 中内部实现的扩展类一般由扩展加载器(ExtClassLoader )实现加载,而程序中的类文件则由系统加载器(AppClassLoader )实现加载。

在类加载后,class 类文件中的常量池信息以及其它数据会被保存到 JVM 内存的方法区中。

连接:

类在加载进来之后,会进行连接、初始化,最后才会被使用. 在连接过程中,有包括验证、准备和解析三个部分.

  • 验证: 验证类是否符合Java规范和JVM规范,在保证符合规范的前提下,避免危害虚拟机安全.
  • 准备: 为类的静态变量分配内存,初始化为系统的初始值.对于final static修饰的变量,直接赋值为用户的定义值. 例如: private final static int value=123; 这个语句,就会在准备阶段分配内存,并初始化值为123, 而如果是private static int value=123;这个阶段valued的值仍然为0.
  • 解析: 将符号引用转为直接引用的过程. 我们知道,在编译时,Java类并不知道所引用类的实际地址,因此只能使用符号引用来代替. 类结构文件的常量池中存储了符号引用,包括类和接口的全限定名、类引用、方法引用以及成员变量引用等,如果要使用这些类和方法,就需要把他们转化成JVM可以直接获取的内存地址和指针,即直接引用.

初始化:

初始化过程是类加载的最后阶段, 在这个阶段,JVM首先将执行构造器方法,编译器会在将.java文件编译成.class文件时,收集所有类初始化代码,包括静态变量赋值语句、静态代码块、静态方法收集在一起组成方法(这个方法执行还是遵循原先代码顺序的)

这里补充个额外的知识点:
Class.forName 和 ClassLoader.loadClass 都能加载类,但是在加载时又有具体区别:
1). Class.forName 默认会执行加载,连接以及初始化操作,即会默认执行静态代码块,静态变量等这些.
2). ClassLoader.loadClass默认就只是将.class文件加载到jvm中,不执行初始化操作.只有在newInstance才会去初始化.

即时编译JIT

在初始化完成后,一个类就可以被正常调用执行, 但这里面还存在着一步优化,就是当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码定义会"热点代码".

为了提高热点代码的运行效率,在运行时,即时编译器JIT会把这些热点代码编译成与本地平台相关的机器码,并进行各层次的优化,然后保存到内存中.由底层硬件直接执行这些代码.

JIT编译其实是一种概念, 在HotSpot虚拟机(Java最常见的jvm实现)中,内置了两种编译器实现.

  • C1: 编译时间短,优化策略简单.
  • C2: 编译时间长,优化策略复杂

HotSpot虚拟机分client端和server端,准确的说应该是分两种类型或两种运行模式,就是两种适用不同业务场景的虚拟机类型。

  • client VM 使用的是C1编译器
  • server VM 使用的是C2编译器

client,server最大的区别就是C1和C2的区别,主要体现在编译策略上:

  • Client启动快,内存占用少,编译快,针对桌面应用程序优化(比如GUI),为在客户端环境中减少启动时间而优化
  • Server启动慢,但是一旦运行起来后,性能将会有很大的提升,因为编译更完全,效率高,针对服务端应用优化.

我们可以在程序启动时,指定使用client或server模式,如下: 指定server模式运行.
在这里插入图片描述

JIT编译优化中的常见技术

在JIT编译的过程中,运用了一些经典的优化技术,可以智能地编译出运行时的最优性能代码.主要介绍以下几种.

方法内联

调用一个方法通常需要经历压栈和出栈,。调用方法是将程序执行 顺序转移到存储该方法的内存地址,将方法的内容执行完后,再返回到执行该方法前的位置.

这种执行操作要求在执行前保护现场并记忆执行的地址,执行后要恢复现场,并按原来保存的地址继续执行。 因此,方法调用会产生一定的时间和空间方面的开销。

那么对于那些方法体代码不是很大,又频繁调用的方法来说,这个时间和空间的消耗会很大。方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用
例如以下方法:


private int add1(int x1, int x2, int x3, int x4) {
    return add2(x1, x2) + add2(x3, x4);
}
private int add2(int x1, int x2) {
    return x1 + x2;
}

最终会被优化为:


private int add1(int x1, int x2, int x3, int x4) {
    return x1 + x2+ x3 + x4;
}

JVM 会自动识别热点方法,并对它们使用方法内联进行优化.但要强调一点,热点方法不一定会被 JVM 做内联优化,如果这个方法体太大了,JVM 将不执行内联操作。而方法体的大小阈值,我们也可以通过参数设置来优化.-XX:CompileThreshold

热点方法的优化可以有效提高系统性能,一般我们可以通过以下几种方式来提高方法内联:

  1. 通过设置 JVM 参数来减小热点阈值或增加方法体阈值,以便更多的方法可以进行内联,但这种方法意味着需要占用更多地内存;
  2. 在编程中,避免在一个方法中写大量代码,习惯使用小方法体;
  3. 尽量使用 final、private、static 关键字修饰方法,编码方法因为继承,会需要额外的类型检查。

逃逸分析

逃逸分析是判断一个对象是否被外部方法或外部线程访问的分析技术,编译器会根据逃逸分析的结果对代码进行优化.

目前根据逃逸分析的结果,笔者所知主要有两种种优化技术.分别是栈上分配、锁消除

   栈上分配

我们知道,在 Java 中默认创建一个对象是在堆中分配内存的,而当堆内存中的对象不再使用时,则需要通过垃圾回收机制回收,这个过程相对分配在栈中的对象的创建和销毁来说,更消耗时间和性能。

而在逃逸分析后,如果发现一个对象只在方法中使用,就可以直接将对象分配在栈上。
以下是通过循环获取学生年龄的案例,方法中创建一个学生对象,我们现在通过案例来看看打开逃逸分析和关闭逃逸分析后,堆内存对象创建的数量对比。


public static void main(String[] args) {
    for (int i = 0; i < 200000 ; i++) {
      getAge();
    }
}

public static int getAge(){
  Student person = new Student("小明",18,30);   
    return person.getAge();
}

static class Student {
    private String name;
    private int age;
   
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

然后,我们分别设置 VM 参数:Xmx1000m -Xms1000m -XX:-DoEscapeAnalysis -XX:+PrintGC 以及 -Xmx1000m -Xms1000m -XX:+DoEscapeAnalysis -XX:+PrintGC,使用 VisualVM 工具,查看堆中创建的对象数量。

然而,运行结果却没有达到我们想要的优化效果,也许你怀疑是 JDK 版本的问题,然而我分别在 1.6~1.8 版本都测试过了,效果还是一样的:

(-server -Xmx1000m -Xms1000m -XX:-DoEscapeAnalysis -XX:+PrintGC)
在这里插入图片描述
(-server -Xmx1000m -Xms1000m -XX:+DoEscapeAnalysis -XX:+PrintGC)
在这里插入图片描述
这其实是因为 HotSpot 虚拟机目前的实现导致栈上分配实现比较复杂,可以说,在 HotSpot 中暂时没有实现这项优化。随着即时编译器的发展与逃逸分析技术的逐渐成熟,相信不久的将来 HotSpot 也会实现这项优化功能。

   锁消除

在非线程安全的情况下,尽量不要使用线程安全容器,比如 StringBuffer。由于 StringBuffer 中的 append 方法被 Synchronized 关键字修饰,会使用到锁,从而导致性能下降。

但实际上,在以下代码测试中,StringBuffer 和 StringBuilder 的性能基本没什么区别。这是因为在局部方法中创建的对象只能被当前线程访问,无法被其它线程访问,这个变量的读写肯定不会有竞争,这个时候 JIT 编译会对这个对象的方法锁进行锁消除。


     public static String getString(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }

小总结

Java 源程序是通过 Javac 编译器编译成 .class 文件,其中文件中包含的代码格式我们称之为 Java 字节码(bytecode)。这种代码格式无法直接运行,但可以被不同平台 JVM 中的 Interpreter 解释执行。由于 Interpreter 的效率低下,JVM 中的 JIT 会在运行时有选择性地将运行次数较多的方法编译成二进制代码,直接运行在底层硬件上。

在 Java8 之前,HotSpot 集成了两个 JIT,用 C1 和 C2 来完成 JVM 中的即时编译。也就分别对应着client和server端, 虽然 JIT 优化了代码,但收集监控信息会消耗运行时的性能,且编译过程会占用程序的运行时间。

到了 Java9,AOT 编译器被引入。和 JIT 不同,AOT 是在程序运行前进行的静态编译,这样就可以避免运行时的编译消耗和内存消耗,且 .class 文件通过 AOT 编译器是可以编译成 .so 的二进制文件的。

今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

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

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

相关文章

Lesson1:初识编程语言、Python环境搭建

一、什么是编程语言 用来和计算机交流&#xff0c;控制计算机&#xff0c;让计算机按照我们的要求做事情&#xff0c;这样的语言叫做编程语言。 Note&#xff1a;编程语言四个字可以拆成两个部分进行理解——编程语言。 所谓语言&#xff0c;它的作用就是交流&#xff0c;向对…

Redis实战—黑马点评(一) 登录篇

Redis实战 — 黑马点评&#xff08;一&#xff09; 登录篇 来自黑马的redis课程的笔记 【黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目】 目录Redis实战 — 黑马点评&#xff08;一&#xff09; 登录篇1. 项目…

深度学习笔记:使用随机梯度下降法识别mnist数据集

深度学习算法实现流程&#xff1a; 1 从训练数据中随机选出一部分数据&#xff0c;称为mini-batch。我们的目标为减小mini-batch损失函数的值 2 计算损失函数关于权重的梯度。梯度方向即为损失函数值减小最快的方向 3 将权重沿梯度下降方向更新 4 重复以上步骤&#xff0c;在…

【自动驾驶汽车技术 | 车载雷达系统】

本文编辑&#xff1a;调皮哥的小助理 1、摘要 自动驾驶汽车传感器系统一般包括4种雷达&#xff1a;激光雷达(Lidar)、毫米波雷达(mmWave Radar)、超声波雷达(Ultrasonic Radar)和红外雷达(Infrared Radar)。目前激光雷达和毫米波雷达是基本和必要的车载传感器设备&#xff0c;…

I.MX6ULL内核开发8:linux设备驱动模型

目录 一、为什么需要设备驱动模型 二、sysfs概述 驱动模型一 驱动模型二 kobject kset kobj_type 一、为什么需要设备驱动模型 早期内核&#xff08;2.4之前&#xff09;没有统一的设备驱动模型&#xff0c;但是照样可以使用&#xff08;之前的led字符设备驱动&#xff…

2023-2-12刷题情况

字母板上的路径 题目描述 我们从一块字母板上的位置 (0, 0) 出发&#xff0c;该坐标对应的字符为 board[0][0]。 在本题里&#xff0c;字母板为board [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”]&#xff0c;如下所示。 我们可以按下面的指令规则行动…

合宙Air103|fbd数据库| fskv - 替代fdb库|LuatOS-SOC接口|官方demo|学习(16):类redis的fbd数据库及fskv库

基础资料 基于Air103开发板&#xff1a;&#x1f697; Air103 - LuatOS 文档 上手&#xff1a;开发上手 - LuatOS 文档 探讨重点 对官方社区库接口类redis的fbd数据库及fskv库的调用及示例进行复现及分析&#xff0c;了解两库的基本原理及操作方法。 软件及工具版本 Luat…

肝了几天的Git入门教程,收获满满

1.简介 谈及版本控制系统&#xff0c;或许大多数程序员最开始接触的都是SVN&#xff08;Subversion&#xff09;&#xff0c;它是一个集中式的版本控制系统&#xff0c;使用的时候需要提供一台的服务器来进行部署&#xff0c;所有的更新与同步操作都需要与这台服务器进行交互&…

windows/linux下Qt可执行程序打包,linux桌面双击运行程序sh脚本

1、windows下Qt打包 windows下Qt的可执行文件打包简单的来说就是利用Qt自带依赖的打包工具windeployqt进行打包&#xff0c;该工具存在Qt安装目录下&#xff0c;执行命令为&#xff1a;windeployqt name.exe 打包依赖文件可参考如下链接中1-7步&#xff0c;后面的步骤是打包依…

156、【动态规划】AcWing ——3. 完全背包问题:二维数组+一维滚动数组(C++版本)

题目描述 原题链接&#xff1a;3. 完全背包问题 解题思路 完全背包相对于01背包来说&#xff0c;对同一个物品可以选择多次。而01背包对同一个物品只能选择一次。 递推公式上的区别&#xff1a;01背包是dp[i][j] max(dp[i - 1][j], dp[i - 1][j - v[i]] w[i])&#xff0c;…

14. 最长公共前缀

14. 最长公共前缀 一、题目描述&#xff1a; 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 1&#xff1a; 输入&#xff1a;strs [“flower”,“flow”,“flight”] 输出&#xff1a;“fl” 示例 2&#xff1a; …

在线支付系列【23】支付宝支付接入指南

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录前言接入指南1. 创建应用2. 绑定应用3. 配置密钥4. 上线应用5. 开通产品沙箱环境开发前准备&#xff08;沙箱环境&#xff09;1. 获取参数、秘钥、证书2. 下载支付宝客户端3. 案例演示前言 在之…

一个自动配置 opengrok 多项目的脚本

前段时间在服务器上配置 opengrok 阅读代码&#xff0c;项目有很多个&#xff0c;一个一个手动配置比较繁琐。 我从搭建 tomcat 和 opengrok&#xff0c;到配置和索引完 5 个 Android 项目&#xff0c;用了差不多一整天。 要是再让我手动配置几个项目&#xff0c;估计真要崩溃…

学习Request和Response这一篇就够啦~

Request(请求) &#xff1a; Request&#xff1a;获取请求数据 Response:设置响应数据 Request继承体系&#xff1a; 使用request对象&#xff0c;查阅JavaEE API文档的HttpServeltRequest接口 Tomcat需要解析请求数据&#xff0c;封装为request对象&#xff0c;并且创建requ…

知识图谱嵌入技术研究综述

作者 张天成 1 , * 田 雪 1 , * 孙相会 1 , * 于明鹤 2 , * 孙艳红 1 , * 于 戈 摘要 知识图谱 是一种用图模型来描述知识和建模事物之间的关联关系的技术。 知识图谱嵌入 作为一种被广泛采用的知识表示方法。 主要思想是将知识图谱中的实体和关系嵌入到连续的向量空间中…

Ansible---playbook剧本

目录 引言&#xff1a;什么是playbook&#xff1f; 一、Playbook 1.1、playbook中的核心元素 1.2、playbook中的基础组件 1.3、playbook格式说明 1.4、实例&#xff1a;httpd服务剧本 二、playbook中的模块 2.1、Templates 模块 2.2、tags 模块 2.3、Roles 模块 引言&…

关于链表中插入结点的操作……

服了&#xff0c;好久没敲链表了&#xff0c;这都忘了 newnode->next cur->next; cur->next newnode; newnode->next cur->next; cur->next newnode; newnode->next cur->next; cur->next newnode; newnode->next cur->next; cur-…

科技常识就像雨衣,要常备哦

科技常识就像雨衣&#xff0c;平常不准备&#xff0c;遇雨成落汤鸡 昨日晨跑遇雨&#xff0c;随身带轻便雨塑料雨衣 趣讲大白话&#xff1a;晴天挖水渠 *********** 信息科技是现代科技的【火车头】 往前看&#xff1a;要关注趋势 往后看&#xff1a;要了解行业历史 在当下&…

数据结构 | 栈与队列

&#x1f525;Go for it!&#x1f525; &#x1f4dd;个人主页&#xff1a;按键难防 &#x1f4eb; 如果文章知识点有错误的地方&#xff0c;请指正&#xff01;和大家一起学习&#xff0c;一起进步&#x1f440; &#x1f4d6;系列专栏&#xff1a;数据结构与算法 &#x1f52…

使用C#编写k8s CRD Controller

本文项目地址&#xff1a;k8s-crd - Repos (azure.com)CRDCRD指的是Custom Resource Definition。开发者更多的关注k8s对于容器的编排与调度&#xff0c;这也是k8s最初惊艳开发者的地方。而k8s最具价值的地方是它提供了一套标准化、跨厂商的 API、结构和语义。k8s将它拥有的一切…