java为什么是半编译半解释型语言

2022-07-30,,,,

1、首先让我们先弄清楚什么是编译语言,什么是解释型语言?

  • 编译型语言:编写好程序以后,首先需要编译器进行编译,统一转化成机器码,然后这个编译完的文件,可以放在操作系统直接执行
  • 解释型语言: 程序是边运行边进行机器码转化(转化完后cpu执)

引用“李白写代码”(【这就好像我们吃一袋瓜子,解释性语言是剥一颗吃一颗,直到吃完;而编译性语言是先把一袋瓜子全部剥完,剥出肉,然后一口吃进去。你说这两种模式对于最后吃的人来说,哪种会比较快,结果不言而知吧!】这里的一袋瓜子其实就是一段程序,吃瓜子的人就是操作系统。)
2、什么是模板解释器、字节码解释器(前提都是直接读取的.class文件)

  • 字节码解释器:读取字节码,转换为c++代码,再由c++代码转换为硬编码,以此类推。其字节码解释器主要的缺点是执行比较慢(Java字节码->c++代码->硬编码);
  • 模板解释器:刚开始的运行原理和字节码解释器一样,只不过模板解释器里面有个特殊的机制-即时编译(即时编译底层原理:1、申请一块内存:可读可写可执行
    2、将处理new字节码的硬编码拿过来
    3、将处理new字节码的硬编码写入申请的内存
    4、申请一个函数指针,用这个函数指针执行这块内存
    5、调用的时候,直接通过这个函数指针调用就可以了)
    (Java字节码->硬编码)

3、模板解释器和字节码解释器的区别及其应用场景

  • 由于模板解释器已经提前生成了字节码到硬编码的映射,当读取到一条字节码,只需要去一张映射表中,找到当前字节码对应的机器指令,这个时候,直接执行机器码。而字节码解释器,是读取到一条字节码,然后把其翻译成C++代码,然后再由C++代码生成机器指令,这样的执行过程较模板解释器来说,执行效率就没那么高。
    补充:上面所说的模板解释器提前生成的字节码到硬编码的过程,其实是在程序启动的时候,生成的,如果当前项目较大的话,启动程序的时候,一定需要很长的时间,因为在启动的时候需要生成字节码到硬编码的映射,当然这样做的目的是,执行效率会更高。

4、JVM的三种运行模式

  • -Xint 纯字节码解释器(1)
  • -Xcomp 纯模板解释器(2)
  • -Xmixed 字节码解释器 + 模板解释器(3)
    注:JVM默认是混合模式,我们可以执行下java -version查看下,可以通过java -Xint version来设置JVM的运行模式为纯字节码解释器。上面三个中执行模式中性能排比是什么呢,321或者是231,直接影响2和3的性能因素是,程序的大小。如果是大程序的话,可以直接采用混合模式,启动时间较快,编译优化器可以根据热点代码等进行优化。


基于上面的模板解释器运行模式,我们在现实生活中会遇到这样的问题,比如,在用解释器解释字节码时,有时候会出现,相同的代码会被执行多次,这样的话,就会导致,花费了很多时间,浪费了很多资源,当然,既然出现问题了,那就有相对应的解决方案,即时编译器诞生了,即时编译是一个动态编译的过程,即时编译器其实就是监测解释器执行的代码块,它会把代码块对应的执行次数记录下来,当执行次数很多的时候,就会优化对应的热点代码,并把对应的机器指令更新在映射表中,这样在下次执行到类似的代码块时,就可以直接运行更新了的的机器指令。接下来让我们再来看下即时编译器吧 ^ _ ^

5、即时编译器(JIT,即时编译器生成的代码就是给模板解释器用的)
HotSpot虚拟机内置了两个即时编译器,分别称为Client Compiler和Server Compiler,习惯上将前者称为C1,后者称为C2。

  • c1
    1、触发的条件相对C2比较宽松:需要收集的数据较少
    2、编译的优化比较浅:基本运算在编译的时候运算掉了
String s1 = "1"; String s2 = "2"; final String s3 = s1 + s2;//按理来说这一步应该会产生一个新的String对象,但是编译器判断是final类型,直接会将s1+s2替换为“12” 
 3、c1编译器编译生成的代码执行效率较C2低 
  • c2
    1、触发的条件比较严格,一般来说,程序运行了一段时间以后才会触发
    2、优化比较深(优化汇编指令)
    3、编译生成的代码执行效率较C1更高
  • 混合编译
    程序运行初期触发C1编译器
    程序运行一段时间后触发C2编译器
    Client 编译器模式下,N 默认的值 1500(N表示热点代码的次数)
    Server 编译器模式下,N 默认的值则是 10000

6、即时编译触发的条件:热点代码(存放在方法区)
在程序运行期间,根据对热点字节码的探测(运行次数超过某个阀值的代码),将这部分热点代码进行特别的优化,将其直接编译为本地机器码执行并缓存。其使用的定期清理算法是LRU,最近最久未使用算法

java -client -XX:+PrintFlagsFinal -version | grep CICompilerCoun //查看当前执行即时编译的线程个数,intx CICompilerCount表示个数 

7、即时编译器是如何运行的呢?

  • 将即时编译任务(即函数弹出栈的次数)写入一个队列中;
  • VM_THREAD 读取任务,并运行
    注:所以即时编译是一个异步的操作

8、即时编译的优化方式

  • 逃逸分析:什么叫逃逸呢?逃逸就是这个对象的作用域不是局部的,逃逸的对象优化工作很困难。

9、基于逃逸分析,JVM开发了三种优化技术

  • 栈上分配:逃逸分析如果是开启的,栈上分配就是存在的(不发生gc的情况下,查看堆上的对象个数,如果是程序中创建的个数,就存在栈上分配)
  • 标量替换:标量:不可再分,java中的基本类型就是标量
import lombok.Data; public class ScalarSubstitution { public static void main(String[] ars) { Point point = new Point(); System.out.println(point.x); //编译器会替换成System.out.println(0); System.out.println(point.y); //同上 } } @Data class Point { public int x; public int y; } 
  • 锁消除:
public void test(){ synchronized (new Object()){ //编译器判定这个对象是个局部变量,是线程私有的,所以就没必要加锁,会直接把锁去掉 System.out.println("zong"); } } 

10、了解了上面的这些知识点以后,对于如果别人问你什么是半编译半解释型语言,知道该怎么回答了么?
编译指的是javac编译生成的字节码文件.class,但为什么是半呢,是因为生成的这个.class文件操作系统不能直接执行,需要解释器进行解释后,才可能运行,所以才把java叫做半编译半解释型语言。

本文地址:https://blog.csdn.net/Zong__Zong/article/details/108020997

《java为什么是半编译半解释型语言.doc》

下载本文的Word格式文档,以方便收藏与打印。