Introduction
java是跨平台的编程语言。所以,它具有自己的编译器,不像其他一些语言需要一来操作系统的浏览器。
在这边文章中,我们将探讨什么是java编译器,以及它是怎么执行java程序的。java不像C语言直接可以从源文件生成一个可以执行的文件,而是将源文件转化成字节码文件(.class),字节码文件是可以被转移到其他任何安装有jre的运行环境去运行的而不会影响系统内部的结构。
java使用独立于操作系统的编译器(javac)去编译源文件得到字节码文件,然后将字节码交由JVM去执行。
编译Compilation/Compile time
在这个过程中,一个源码文件被编译器从源代码编译成字节码文件,以.class结尾。一个源代码文件对应一个.class文件。等到编译结束时,编译器会对字节码基本的语法和语义进行校验。
执行Execution/Runtime
当编译器校验完程序的错误,程序员也修改好了源文件里面相应的错误并重新编译之后。重新编译好的字节码会被加载到JVM中,加载到JVM之后,主要会经过以下三个主要步骤:
1、ClassLoader 类加载器
主程序字节码会被类加载器加载到系统的内存中去,同时,所有需要的或者被饮用的类也会被类加载器加载进内存。
类加载器。
类加载器分为两种类型:一种是原始加载器(primordial class loader ),一种是非原始加载器。
原始加载器就是系统自身自带的加载器,是加载器的加载器。原因是每个类有它的加载器,但是加载器本身也是一个类,那么它也需要一个加载器去加载它,原始加载器就是系统中用来加载类加载器的加载器。原始加载器通常是用C编写,并且不会在java的上下文中显现出来。
某些类,例如java。*包中定义的类,对于Java虚拟机和运行时系统的正确运行必不可少。它们通常被称为基类。由于历史原因,所有此类都具有一个为null的类加载器。这个空类加载器也许是原始类加载器存在的唯一标志。实际上,将空类加载器简单地视为原始类加载器更为容易。
简言之,原始加载器和非原始加载器的区别就是,原始加载器是jvm自带的,是不能被我们所修改的。那么,非原始加载器是我们可以自定义的,类的加载过程完全是我们自己掌控的。
2、Bytecode Verifier字节码校验器
这是整个编译过程中最关重要的一个步,它的作用是去检查代码是否会具有破坏性。
字节码校验器遍历字节码,构造类型状态信息,并验证所有字节码指令的参数类型。
测试的范围从简单的验证代码片段格式正确到通过简单的定理证明者传递每个代码片段以证明其遵循规则:
- 它没有伪造指针,
- 它没有违反访问限制,
- 它按原样访问对象(例如,InputStream对象始终用作InputStream,而从不用作其他任何对象)。
一种安全的语言加上生成的代码的运行时验证,可以建立一套基本的保证,保证不会违反接口。
字节码校验器充当一种看门人的角色:它确保传递给Java编译器的代码处于适合的状态,可以执行并且可以运行,而不必担心破坏Java解释器。导入的代码在通过验证者的测试之前,不允许以任何方式执行。一旦校验完成后,就会知道许多重要的属性:
- 没有操作数栈上溢或下溢
- 已知所有字节码指令的参数类型始终正确
- 已知对象字段访问是合法的-私有,公共或受保护的
简言之,就是会去校验下面几个方面:
- 一个参数变量是否被合理地初始化
- 使用的方法会返回要求返回的适当的值
- 运行时栈未溢出。
- 不会违反访问权限修饰词Access specifier(modifier)(如:default,private,protected,public)
如果字节码校验器发现有任何一步出现错误,就会中止类加载的过程并且返回一个error。
oracle 官方关于字节码校验器的说明
3、Just-in-Time Compiler JIT编译器
这是编译过程的最后一步,JIT编译器将字节码转化成机器语言,也就是0和1,然后将机器语言指令传递给本机,来帮助提高Java程序的性能。
JIT编译器默认情况下处于启用状态,并在调用Java方法时被激活。 JIT编译器将该方法的字节码编译为本地机器代码,“及时”编译以运行。编译方法后,JVM会直接调用该方法的已编译代码,而不是对其进行解释。从理论上讲,如果编译不需要处理器时间和内存使用量,则编译每种方法都可以使Java程序的速度接近本机应用程序的速度。
而JIT编译确实需要处理器时间和内存使用率。 JVM首次启动时,将调用数千种方法。即使程序最终达到了非常好的峰值性能,编译所有这些方法也会严重影响启动时间。
编译字节码成机器码的过程是相当耗时的,那么为何还要对字节码进行编译呢?
原因在于,当一段代码被频繁调用的时候,比如一个循环中的循环体,这段代码将会被执行很多次,故JIT编译器编译好的机器码最终交交还给物理机执行,而不是由JVM执行。
JIT编译器原理的详细解释
以下是Oracle官方的流程图,更为准确一些。
问题补充:JIT是不是属于JVM的一部分?
下图是整个JDK、JRE和JVM的关系,我们可以看到在JVM(java virtual machine)处,它是包含Java HotSpot Client Compiler和Java HotSpot server Compiler的。
Client Compiler 与 Server Compiler
我们可以用修改参数的方式来指定采用Client模式和Server模式,默认情况下是mixed模式。
java -Xint 解析 java -Xcomp 编译
Client Compiler和Server Compiler会实现分层编译(Since JDK1.7)。
第0层 程序解析执行,解析器不开启性能监控,可触发第一层编译;
第1层 编译成本地相关代码,进行简单优化;
第2层 除编译成本地相关代码外,还进行成编译耗时较长的优化。
Client Compiler获得更高的编译速度
Server Compiler获得更好的编译质量,无须承担性能监控任务
这边的HotSpot Compiler就是我们所说的JIT Compiler。
何解?
首先我们要分清三个重要概念
三个概念:
1. interpreter(解释器/翻译器)
- interpreter(解释器/翻译器):解释器会一行一行地解释并执行字节码。它的缺点是当一个方法或代码片段(这边主要是循环体)被多次调用时,每次都需要去执行该过程。
2. Just-In-Time Compiler(JIT编译器)
- Just-In-Time Compiler(JIT编译器) :它被用来提高解释器的执行效率。JIT编译器会编译整个字节码并将它转化成native code也就是机器码,所以当解释器碰到重复调用这个方法或代码片段时,JIT compiler就会直接提供之前编译好的机器码,就不需要再次对他进行解释,效率就得到了提升。
3. HotSpot code(热点代码)
- HotSpot code(热点代码):在解释器解释执行一段代码的时候,并不知道该代码会被多次调用,所以刚开始并不会主动去编译这段字节码(解释执行比编译执行要快很多)。如果一段代码被调用超过一定频率,也就是被频繁调用的时候,这段代码就会被JIT编译器识别为“热点代码”。由于这段代码被多次解释执行,JIT编译器也会在每次编译的时候作出相应的优化。
热点代码包括两类:
- 被频繁调用的方法
- 频繁执行的循环体
触发JIT阈值的检测:
- 方法调用计数器(Invocation Counter):检测方法调用的次数
- 回边计数器(Back Edge Counter):记录代码块循环的次数
出发了JIT编译并完成编译之后,这段代码的入口就会被系统自动改为新的编译入口,就会直接调用编译好的版本。
结合以上的基本概念,我们来谈谈JVM中代码执行的几种模式。
JVM中代码执行的三种模式
1. 解释器模式
一条一条地读取,解释并且执行字节码指令。因为它一条一条地解释和执行指令,所以它可以很快地解释字节码,但是执行起来会比较慢,没有JIT的配合下效率不高。
解释器模式优缺点
- 程序启动时首先发挥作用,解释执行Class字节码;
- 省去编译时间,加快启动速度;
- 但执行效率较低;
2. JIT编译器模式
即时编译器把整段字节码不加筛选的编译成机器码不论其执行频率是否有编译价值,在程序响应时间的限制下,没有达到最大的优化。
JIT编译器模式优缺点
- 程序解释运行后,JIT编译器逐渐发挥作用;
- 编译成本地代码,提高执行效率;
- 但占用程序运行时间、内存等资源;
3. 混合模式
在解释执行的模式下引入编译执行,刚好可以弥补相互的缺点,达到更优的效果。
参考链接🔗:
https://www.jianshu.com/p/25d17b7b8ff0
https://blog.csdn.net/sinat_37138973/article/details/78438806
https://blog.csdn.net/sunxianghuang/article/details/52094859
https://blog.csdn.net/HarderXin/article/details/103924865
https://blog.csdn.net/HarderXin/article/details/103924865
https://www.icode9.com/content-1-191324.html
http://blog.sina.com.cn/s/blog_1645e034e0102yg20.html
拓展链接🔗:java内存
https://dzone.com/articles/evolution-of-the-java-memory-architecture-java-17
https://www.cnblogs.com/rafx/p/4887145.html