Java虚拟机启动过程解析

当我们在编写Java应用的时候,很少会注意Java程序是如何被运行的,如何被操作系统管理和调度的。带着好奇心,探索一下Java虚拟机启动过程。

Java源代码Java字节码Java虚拟机操作系统四个角度分解启动过程。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("HelloWorld!");
    }
}

利用Java环境提供的可执行命令 javac将源代码编译成字节码文件,编译后的字节码文件与平台无关,可跨平台运行。注意区分 javac命令是一个独立的编译应用,源代码编译完成,进程终止。 java命令启动的虚拟机进程的编译过程是将字节码指令编译成汇编指令(二进制指令)。

Java字节码无法直接在操作系统上创建进程,因此需要借助已经启动的虚拟机进程来解析字节码,处理字节码有两种常见方式: 解释型编译型

在命令行中每运行 java命令代表启动一个Java虚拟机进程,各虚拟机相互独立,通过命令行参数分别对虚拟机进程进行配置。

Java虚拟机准备启动完毕后,便可以依次解析字节码指令,正式运行 Java代码部分。

操作系统通过进程管理和调度Java虚拟机,无法感知虚拟机间接解析Java字节码部分。Java字节码通过虚拟机的抽象,完成了在操作系统上运行。

当运行Java应用时,需要先安装Java环境,然而安装的Java环境与Java应用有什么关系,Java应用是如何运行起来的,下面一探究竟。

二进制可执行程序 ${JAVA_HOME}/bin/java是C++编写经过GCC编译器编译后形成的,探索Java虚拟机的运行原理,首先需要找到相应的源码。

当在安装Java环境时,会看到一个 src.zip压缩文件,解压后里面 launcher/java.c文件便是可执行文件 java命令的主要源码。

虚拟机的启动入口位于 launcher/java.cmain方法,整个流程分为如下几个步骤: 配置JVM装载环境;解析虚拟机参数;设置线程栈大小;执行Java main方法

从操作系统加载环境变量、硬件信息等运行环境信息,为后续创建JVM进程做准备。

装载完JVM环境之后,需要对启动时命令行参数进行解析,该过程通过 ParseArguments方法实现,并调用 AddOption方法将解析完成的参数保存到JavaVMOption中。

比如常见的JavaVMOption参数在此步骤解析:

 -Xms:设置堆的初始值InitialHeapSize,也是堆的最小值;
 -Xmx:设置堆的最大值MaxHeapSize;

JVM调优各参数解析便是在此步骤完成的。

线程栈大小确定后,通过 ContinueInNewThread方法创建新线程,并执行JavaMain函数,大概流程如下:

InitializeJVM方法调用InvocationFunctions的CreateJavaVM方法,即调用JVM.dll函数JNI_CreateJavaVM,新建一个JVM实例,该过程比较复杂。

通常在命令行中运行如下命令即指明入口类路径

直接指名入口类路径
java HelloWorld.class
通过包类配置入口类路径
java -jar HelloWorld.jar

通过GetStaticMethodID方法查找指定main方法名的静态方法。

通过 JavaCalls::call回调执行main方法。需要注意的是,这里执行main方法不是Java语言的方法,是经过虚拟机解释(或者编译)后,操作系统能够理解的二进制可执行方法。

iconst_1    将 1 放入栈顶
iconst_1    将 1 放入栈顶
iadd        将栈顶的 2 个数相加后结果放入栈顶
istore_0    将相加的结果放入局部变量表

基于栈的指令集优点是虚拟机解释器是可跨平台移植的,换句话说不同平台的虚拟机解释器代码可以复用。

mov eax,1 把 EAX 寄存器的值设为 1
add eax,1 再把这个值加 1 ,结果保存在了 EAX 寄存器

基于寄存器指令集的优点是执行速度相对于栈较快,原因是出栈入栈本身就涉及了大量的指令,而且栈是在内存中实现的,更底层的汇编指令性能更高。

基于寄存器指令集的缺点是虚拟机解释器是不可跨平台移植,需要针对不同平台的虚拟机做不同实现。考虑到不同平台已经使用不同的虚拟机程序,因此此过程多用户透明。

虚拟机通过解释器来翻译字节码文件中的指令比较顺其自然,可是对于服务器端高频执行的程序来说,中间的翻译过程相对耗时。解释字节码的方式适用于对启动性能要求高,并且执行频率较低的应用程序。

最初,JVM 中的字节码是由解释器( Interpreter )完成编译的,当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会把这些代码认定为 热点代码

为了提高热点代码的执行效率,在运行时,即时编译器(JIT,Just In Time)会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,然后保存到内存中。

在 HotSpot 虚拟机中,内置了两种 JIT,分别为 C1 编译器C2 编译器,这两个编译器的编译过程是不一样的。

C1 编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,也称为 Client Compiler,例如,GUI 应用对界面启动速度就有一定要求。

C2 编译器是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序,也称为 Server Compiler,例如,服务器上长期运行的 Java 应用对稳定运行就有一定的要求。

分层编译将 JVM 的执行状态分为了 5 个层次:

第 0 层:程序解释执行,默认开启性能监控功能(Profiling),如果不开启,可触发第二层编译;
第 1 层:可称为 C1 编译,将字节码编译为本地代码,进行简单、可靠的优化,不开启 Profiling;
第 2 层:也称为 C1 编译,开启 Profiling,仅执行带方法调用次数和循环回边执行次数 profiling 的 C1 编译;
第 3 层:也称为 C1 编译,执行所有带 Profiling 的 C1 编译;
第 4 层:可称为 C2 编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

通常情况下,C2 的执行效率比 C1 高出30%以上。

在 Java8 中,默认开启分层编译。如果只想开启 C2,可以关闭分层编译( -XX:-TieredCompilation),如果只想用 C1,可以在打开分层编译的同时,使用参数: -XX:TieredStopAtLevel=1

通过 java -version命令行可以查看到当前虚拟机解析字节码的方式, mixed mode表示既有解释模式也有即是编译模式。

java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)

mixed mode代表是默认的混合编译模式,除了这种模式外,我们还可以使用 -Xint参数强制虚拟机运行于只有解释器的编译模式下;也可以使用参数 -Xcomp强制虚拟机运行于只有 JIT 的编译模式下。

仅使用解释模式

通过命令 java -Xint -version设置仅使用解释模式, interpreted mode表示解释模式。

java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, interpreted mode)

仅使用编译模式

通过命令 java -Xcomp -version设置仅使用编译模式, compiled mode表示编译模式。在编译模式下,程序启动能感觉到明显的卡顿。

java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, compiled mode)

通过对Java虚拟机启动过程的解析,特别是 即时编译环节的理解,Java应用运行并不慢。当应用中热点代码普遍被编译成汇编指令(二进制可执行命令)存放于内存中时,可近似达到C语言原生程序的运行速度。

随着算力与内存成本日渐降低,通过空间复杂度置换时间复杂度的策略显然是合理的,使用Java语言编写需求万千变化的应用是第一选择:既有跨平台、内存安全、框架生态丰富的优点,也在运行效率方面积极改善,这种折中选择与市场反馈保持一致。

Original: https://www.cnblogs.com/javazhishitupu/p/16316869.html
Author: Java知识图谱
Title: Java虚拟机启动过程解析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/574853/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • idea 运行 tyarn 命令提示系统禁止运行脚本

    无法加载文件D:……….(报错信息。。。),因为在此系统上禁止运行脚本,有关详细信息,请参阅 https:/go.microsoft.com/f…

    Java 2023年6月15日
    092
  • 力扣刷题之路——-统计数组中的元素

    参考刷题顺序: 力扣刷题顺序 涉及题目 645. 错误的集合 697.数组的度 448.找到所有数组中消失的数字 442.数组中重复的数据 41.缺失的第一个正数 274.H指数 …

    Java 2023年6月5日
    0102
  • 图解|从根上彻底理解MySQL的索引

    这是图解MySQL的第4篇文章,这篇文章会让你 明白什么是索引,彻底理解B+树和索引的关系; 彻底理解主键索引、普通索引、联合索引; 了解什么是HASH索引,InnoDB和MyIS…

    Java 2023年6月7日
    0100
  • 迭代器Iterator的使用方法(Java)

    迭代器是一种经典的设计模式。 用于在不需要暴漏数据是如何保存在数据结构中的细节的情况下,遍历一个数据结构。Collection接口继承自Iterable接口。所以说,实现了Coll…

    Java 2023年6月5日
    083
  • Liquibase-数据库脚本版本管理控制

    1. 简介 Liquibase是一个用于跟踪、管理和应用数据库变化的开源的数据库重构工具。它将所有数据库的变化(包括结构和数据)都保存在XML文件中,便于版本控制。 Liquiba…

    Java 2023年6月7日
    096
  • NoteOfMySQL-12-备份与还原

    一、备份概述 备份不是单纯的复制数据,因为这样无法留下历史记录和系统的DNS或Registry等信息。完整的备份应包括自动化的数据管理与系统的全面恢复,即备份=复制+管理。 1. …

    Java 2023年6月5日
    074
  • window server 2019环境下将nginx配置为开机自启动服务

    公司window服务器上面有个nginx在跑,重启服务器后没有自动启动,需要手动运行nginx,如果是非正常重启业务可能就中断了 1、下载WinSW(window service …

    Java 2023年5月30日
    065
  • Docker 安装&卸载

    不同版本可能有差异具体信息查看官网 官网:https://docs.docker.com/engine/install/centos/ 安装 #环境准备 #查看环境 uname -…

    Java 2023年6月5日
    0100
  • 一次XGBoost性能优化-超线程影响运算速度

    一、问题背景 一个朋友在使用 XGBoost 框架进行机器学习编码,他们的一个demo, 在笔记本的虚拟机(4核)运行的时候,只要8s, 但是在一个64核128G 的物理机上面的虚…

    Java 2023年5月30日
    083
  • Java正则表达式

    不包含thumb.jpg,但包含_HH_,并以.jpg结尾 不包含_Check.xml,但以.xml结尾 Original: https://www.cnblogs.com/gis…

    Java 2023年5月29日
    068
  • 31.使用计时器,分析服务端recv的性能

    服务端: 使用计时器,计时每秒钟调用了多少次recv,收到了多少数据包。recv每次接收1个字节。 客户端:使用计时器,计时每秒钟调用了多少次send函数。 调整客户端的线程数,客…

    Java 2023年5月30日
    065
  • nginx server中的root和location的root的区别

    自己发现公司服务器上的nginx的配置文件里,server中有个root, location中也有root。当直接访问域名,后面什么都不加, 发现走的是location里面的roo…

    Java 2023年5月30日
    078
  • Spring Cloud Gateway 内置过滤器 filter

    https://docs.spring.io/spring-cloud-gateway/docs/2.2.6.RELEASE/reference/html/#gatewayfilt…

    Java 2023年5月30日
    090
  • 详细分析Java中断机制-转载

    引言 当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时……都需要通过一个线程去取消另一个线程正在执行的任…

    Java 2023年5月29日
    064
  • Spring Cloud Gateway 路由定位器

    1 时刻与技术进步,每天一点滴,日久一大步!!! 本博客只为记录,用于学习,如有冒犯,请私信于我。 Original: https://www.cnblogs.com/myitne…

    Java 2023年5月30日
    060
  • Java全栈系列笔记

    Java全栈系列笔记 全部文档、项目、源码: github:https://github.com/name365/Blog-Java 码云:https://gitee.com/ya…

    Java 2023年5月29日
    076
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球