3分钟读懂java程序的执行原理

学校里的老师都会教java是一种高级语言,会由编译器编译执行。脱离了学校,回过头来想想,当时老师说的可真是简单,实际上理解起来还是需要下一番功夫的。

1. 编译还是解释?

老师都告诉我们了,java要编译才能执行。这确实不能够被反驳。我们知道java的运行离不开java虚拟机(jvm),然而jvm的实现却又很多种类。我们这里就以sun公司提供的hot spot虚拟机举例,java的执行过程中也确确实实是由解释的过程存在的。

抛开hot spot的各种历史版本,就以当下的实现来说。java程序现有前端编译器编译成了字节码文件,翻译成人话就是执行javac生成了.class的文件。此处使用的编译器我们叫做前端编译器。

之后,对于生成的字节码,jvm又有两种执行策略。第一种,直接解释字节码文件,执行程序,这种方式搞简单粗暴,然而运行的时候稍慢。另一种,让JIT进行即时编译,将java字节码翻译成机器码再执行,这么做当然会复杂一些,消耗的资源会多一些,但是直接运行机器码的速度是远远超过前者的。

在现在的hot spot虚拟机中,综合了上述两种策略。在前端编译器编译生成字节码文件后,jvm首先会采用解释的方式运行程序,然后会在解释执行的过程中探测到经常使用的方法或者循环体。jvm会将这些高频的方法或者循环体作为热点(hot spot),JIT(just in time)编译器会将热点代码编译成机器码,存放在缓存中。就这样解释和编译的综合,保证了资源消耗和运行速度的平衡。

为了应证上述原理,我们可以做个实验。实验使用JMH,一种性能测试的工具,可以测出方法执行的耗时。我们将写一段小程序,然后循环执行,循环执行分6个Iteration,每个Iteration的执行3秒钟。最后统计程序的平均执行时间。

测试程序:

public int mul() {
        int result = 1;
        for(int i = 1; i < 1000; i++) {
            result = result * i;
        }
        return result;
    }

测试结果:

可以看出三次的实验,都是在刚开始时的平均时间最长。因为,当程序刚开时执行的时候,采用的是解释执行,随着程序的重复执行,hot spot探测到高频语句并将其翻译成机器码,如此耗时便会减少。

2. .class文件?

上文提到了字节码。字节码是经过前端编译器编译之后生成的一种中间状态码,操作系统不认识这种东西,但是java认识。在class文件里面就装满了这样的字节码。做个实验看看字节码长什么样:

先写段helloworld:

public class Hello {
    public static void main(String[] a) {
        System.out.println("Hello world");
    }

}

再javac编译以下,生成了Hello.class文件。

再使用javap -c Hello.class:

字节码中第一列的数字是程序计数寄存器记录的指令地址,第二列是执行的指令。

字节码是java才认识的东西,我们看不懂没关系。

3. javac.exe?java.exe?

老师曾经告诉我们javac就是编译,java就是执行。这话说的很简单,但可以引申出很多东西。

姑且不考虑其他平台,javac.exe是jdk提供的默认的编译器,这东西将.java文件编译成.class文件(将源码转成字节码)。大家应该明白的是,事实上,编译器并不仅仅只有javac这一种,javac可以说是我们平时很少使用的编译器了。我们平时使用集成开发环境来开发java程序,而一般IDE默认使用的编译器不是javac。比如,很久之前我们也把eclipse这样的集成开发环境叫做编译器,这不是没有原因的,因为eclipse使用了独立的编译器ECJ。而后来tomcat等容器也都支持了ECJ编译器。

对于java.exe,它会调用java默认的虚拟机hot spot。我们如果搜索jdk或者jre的安装文件夹,可以发现有个叫做jvm.dll的文件,java.exe在执行时,调用了这个东西。而后jvm便开始执行自己的逻辑。与javac类似,我们也可以使用不一样的jvm,比如之前oracle出的JRockit(已挂),IBM的J9 。

4. native方法搞特殊化?

native关键字修饰的方法实际上是告诉大家,该方法里的实现的主要功能不是由java完成的,而是由c或者c++完成的,该方法只作了一件事,就是调用了c或者c++程序。java处于一个比较高层的位置,很多操作系统级别的底层功能并不能用java直接实现,所以智能依靠调用其他底层语言来实现相关功能。

再做个实验,对于方法System.currentTimeMillis(),使用反编译插件打开可以看到System类里面的实现,currentTimeMillis()这个方法被native关键字修饰,但是没有实现。它并非是一个接口,而是会调用操作系统提供的功能。

对于native方法,在执行javac之后,同样会生成.class文件。之后不再直接编译执行,而是javah -jni,生成.h文件。之后使用本地语言c或者c++实现功能并包含.h的头文件,生成动态连接库,生成dll文件。调用完成。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值