热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java虚拟机启动过程解析

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

一、序言

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

1、素材准备

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

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("HelloWorld!");
    }
}
2、源代码生成字节码

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

3、虚拟机解析字节码

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

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

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

4、操作系统管理虚拟机

操作系统通过进程管理和调度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进程做准备。

(二)命令行参数解析

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

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

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

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

(三)执行main方法

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

1、新建JVM实例

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

2、加载入口类

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

# 直接指名入口类路径
java HelloWorld.class
# 通过包类配置入口类路径
java -jar HelloWorld.jar
3、查找main方法

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

4、执行main方法

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

三、解析字节码

(一)解释字节码

1、基于栈指令集
iconst_1    将 1 放入栈顶
iconst_1    将 1 放入栈顶
iadd        将栈顶的 2 个数相加后结果放入栈顶
istore_0    将相加的结果放入局部变量表

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

2、基于寄存器指令集
mov eax,1 把 EAX 寄存器的值设为 1
add eax,1 再把这个值加 1 ,结果保存在了 EAX 寄存器

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

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


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

(二)编译字节码

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

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

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

1、C1 编译器

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

2、C2 编译器

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

3、分层编译

分层编译将 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语言编写需求万千变化的应用是第一选择:既有跨平台、内存安全、框架生态丰富的优点,也在运行效率方面积极改善,这种折中选择与市场反馈保持一致。


推荐阅读
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • 卓盟科技:动态资源加载技术的兼容性优化与升级 | Android 开发者案例分享
    随着游戏内容日益复杂,资源加载过程已不仅仅是简单的进度显示,而是连接玩家与开发者的桥梁。玩家对快速加载的需求越来越高,这意味着开发者需要不断优化和提升动态资源加载技术的兼容性和性能。卓盟科技通过一系列的技术创新,不仅提高了加载速度,还确保了不同设备和系统的兼容性,为用户提供更加流畅的游戏体验。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • Netty框架中运用Protobuf实现高效通信协议
    在Netty框架中,通过引入Protobuf来实现高效的通信协议。为了使用Protobuf,需要先准备好环境,包括下载并安装Protobuf的代码生成器`protoc`以及相应的源码包。具体资源可从官方下载页面获取,确保版本兼容性以充分发挥其性能优势。此外,配置好开发环境后,可以通过定义`.proto`文件来自动生成Java类,从而简化数据序列化和反序列化的操作,提高通信效率。 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 在处理大图片时,PHP 常常会遇到内存溢出的问题。为了避免这种情况,建议避免使用 `setImageBitmap`、`setImageResource` 或 `BitmapFactory.decodeResource` 等方法直接加载大图。这些函数在处理大图片时会消耗大量内存,导致应用崩溃。推荐采用分块处理、图像压缩和缓存机制等策略,以优化内存使用并提高处理效率。此外,可以考虑使用第三方库如 ImageMagick 或 GD 库来处理大图片,这些库提供了更高效的内存管理和图像处理功能。 ... [详细]
  • 分布式开源任务调度框架 TBSchedule 深度解析与应用实践
    本文深入解析了分布式开源任务调度框架 TBSchedule 的核心原理与应用场景,并通过实际案例详细介绍了其部署与使用方法。首先,从源码下载开始,详细阐述了 TBSchedule 的安装步骤和配置要点。接着,探讨了该框架在大规模分布式环境中的性能优化策略,以及如何通过灵活的任务调度机制提升系统效率。最后,结合具体实例,展示了 TBSchedule 在实际项目中的应用效果,为开发者提供了宝贵的实践经验。 ... [详细]
  • Java中高级工程师面试必备:JVM核心知识点全面解析
    对于软件开发人员而言,随着技术框架的不断演进和成熟,许多高级功能已经被高度封装,使得初级开发者只需掌握基本用法即可迅速完成项目。然而,对于中高级工程师而言,深入了解Java虚拟机(JVM)的核心知识点是必不可少的。这不仅有助于优化性能和解决复杂问题,还能在面试中脱颖而出。本文将全面解析JVM的关键概念和技术细节,帮助读者全面提升技术水平。 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • Visual Studio Code (VSCode) 是一款功能强大的源代码编辑器,支持多种编程语言,具备丰富的扩展生态。本文将详细介绍如何在 macOS 上安装、配置并使用 VSCode。 ... [详细]
  • 在C#编程中,数值结果的格式化展示是提高代码可读性和用户体验的重要手段。本文探讨了多种格式化方法和技巧,如使用格式说明符、自定义格式字符串等,以实现对数值结果的精确控制。通过实例演示,展示了如何灵活运用这些技术来满足不同的展示需求。 ... [详细]
  • 如何精通编程语言:全面指南与实用技巧
    如何精通编程语言:全面指南与实用技巧 ... [详细]
  • 如何在Spark数据排序过程中有效避免内存溢出(OOM)问题
    本文深入探讨了在使用Spark进行数据排序时如何有效预防内存溢出(OOM)问题。通过具体的代码示例,详细阐述了优化策略和技术手段,为读者在实际工作中遇到类似问题提供了宝贵的参考和指导。 ... [详细]
author-avatar
VI_宁小豆2502874431
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有