一、序言

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

到此这篇关于Java虚拟机启动过程解析的文章就介绍到这了,更多相关Java虚拟机启动内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Java虚拟机启动过程探索的更多相关文章

  1. XCode 5远程调试OS X应用程序

    我正在使用XCode5.0.2在OSX10.9上开发一个应用程序并获得了一个我无法在这台开发机器上重现的错误报告.但是,我有一个10.7虚拟机出现崩溃,所以我想调试那里没有在这个VM中安装XCode.我搜索了有关远程调试的信息,但我没有得到有用的答案.我甚至担心它根本不受支持.但无论如何我还是要问一下.或者,除了执行完整的XCode安装等之外,还有哪些其他选项来调试这样的问题?

  2. cinder swift的区别

    [原]OpenStack入门以及一些资料之2014-4-29阅读1144评论0注:本文内容均来自网络,我只是在此做了一些摘抄和整理的工作,来源均有注明。它拥有自己的文件系统,通过网络文件系统NFS或通用文件系统CIFS对外提供文件访问服务。Raid,不同的raid等级在增加数据可靠性以及增加存储器(群)读写性能间取得平衡。卷组描述区域,和磁盘将包含分区信息的元数据保存在位于分区的起始位置的分区表中一样,逻辑卷以及卷组相关的元数据也是保存在位于物理卷的VGDA中。

  3. OpenStack中Swift和cinder区别

    swift是objectstorage,将object存储到bucket里,你可以用swift创建container,然后上传文件,例如视频,照片,这些文件会被replication到不同服务器上以保证可靠性,swift可以不依靠虚拟机工作。如果你把这个虚拟机terminate了,这个volume和里边的数据依然还在,你还可以把它接到其他虚拟机上继续使用里边的数据。cinder创建的volume必须被接到虚拟机上才能工作。

  4. Swift属性观察者在协议扩展?

    换句话说,我可以观察协议扩展中的属性的更改吗?这并不意味着它是不可能实现的,但如果我们有这样的话可能会有点令人惊讶.

  5. 解决Swift 3中缺少递归协议约束的问题

    Swift3目前对“递归协议约束”有一个限制.有一个公开的问题here,在here,here和here有类似的讨论.但是,我仍然没有看到应该如何解决这个限制.可能吗?或者我需要开始引入较不严格的协议,直到在Swift中实现?会出现.然而,通过这种方法,我们可以得到正确的类型,而无需做很多专业化.当然,可以添加更多的协议来获得更多的抽象,但同样的解决方案将会适用.由于某些原因/语言缺陷,您必须在View.foo中分配委托时使用显式转换:viewmodel.delegate=selfas?

  6. Android VM不允许我们分配xx字节

    我正在开发一款安卓游戏.当我尝试使用3张图像作为背景时问题就出现了.图像为1280x720px和100kb大.图像真的不是那么大,所以我有点困惑,为什么它们应该导致内存问题.注意:屏幕分辨率为800×400,因此我无法通过因子2调整图像大小,因为它是suggestedonandroiddeveloper注意:我正在使用HTC欲望手机(这里崩溃来了),我也尝试过在三星galaxyS1和三星上运行正常

  7. 在Android中编译java文件后,注释会发生什么?

    Android编译器如何工作?它是否在编译时删除了Java代码和AndroidXML文件中的注释?

  8. 为什么Android上的每个应用程序都有单独的VM(Dalvik / ART)实例?

    正如标题所述,为什么Android上的每个App都有单独的VM实例?(需要它)如果Android操作系统选择了单个虚拟机运行所有应用程序的模型,会发生什么?解决方法在单个进程中运行多个应用程序不起作用的原因有很多;这是两个:安全区.两个不相互信任的应用程序不应该能够查看彼此的内存,即使它们使用本机代码或反射.失败隔离.如果进程泄漏内存并崩溃,则只会损害自身.

  9. android – 如何选择最佳图像大小不超过VM预算?

    在我的应用用户中,选择图像和程序可让用户对图像进行更改.由于有很多不同的Android设备,我的程序在一些设备上崩溃,这些设备的堆大小较少.我想计算用户手机的最佳尺寸,以免因VM预算而崩溃.我添加了“PicsayPro”的截图,它正是我正在寻找的.我知道“BitmapFactory.Options”我唯一的问题是找到一种方法来决定图像尺寸,这不会因为VM预算而导致应用程序崩溃.解决方法计算手机剩余

  10. Virtualbox上的Android x86中的蓝牙

    解决方法VirtualBox能够共享USB设备.您的蓝牙适配器可能通过USB内部连接也可能不通过USB连接.我有两个Thinkpad,里面都有蓝牙,只有一个在USB上.许多“我想测试我的Android蓝牙应用程序”解决方案中提到的技巧假设您的蓝牙设备使用USB,但是当它没有时它将无法工作.

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部