暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java程序是怎么跑起来的

一十二章经 2020-06-21
739

我们在学习Java的时候首先是在自己本地安装一个名叫JDK(Java Developent Kit)的东西,然后就是各种环境配置。那么,我们编写的Java代码是怎么通过我们安装的JDK然后在自己的机器上运行起来的呢?本篇的主要目的就是为了讲解我们的编写的Java源代码是怎么一步一步让计算机执行

了解JDK

先自顶向下来理解我们编写好的Java代码运行过程。首先,机器需要提供一个运行环境给Java程序运行也就是我们的JRE(Java Runtime Environment),JRE提供了一些源代码库JVM(Java Virtual Machine),有了这两个东西后我们算是有了运行环境。

JDK中还有一些Development Tool的工具提供我们使用,比如下面列举的一些工具是本人经常用的

  • javac.exe:Java编译工具(Java Compiler),将.java文件编译成.class文件

  • java.exe:执行.class文件或.jar文件

  • javap.exe:Java反编译工具,主要用于根据Java字节码文件反汇编为Java源代码文件。

有了以上的准备,Java程序就能就在你的机器上跑起来了,因此我们也知道了JDK的组成成分

JDK = JRE + Development Tool = JVM + Library Classes + Development Tool

Java程序与机器的桥梁

在上面的JDK组成成分中,JVM(Java虚拟机)充当了非常重要的角色,它不仅作为程序与机器的桥梁还为Java程序员提供了很多便利,比如说我们写程序时不用关心内存的分配,内存的释放(深入探讨可以参考Java生态系统的基石—Jvm)支持了Java程序跨平台运行。

Java程序的跨平台运行

对于跨平台运行我们首先需要理解运行两个字。我们现在的计算机硬件结构叫冯诺依曼体系结构计算机也叫存储程序计算机,这套体系有以下硬件结构组成

  • 处理器单元

  • 控制器单元

  • 存储器

  • 输入输出设备(I/O)

其中处理器单元和控制器单元被继承在一个充满晶体管的电路板上面了,也就是我们熟悉的CPU。很显然,根据这套体系,我们的程序能够跑起来需要解决的问题就是CPU怎么才能执行Java代码

对于CPU而言,无论是什么类型的计算机语言(C,汇编等等)我只认识0、1这两个字符,所以无论什么语言最终都必须转换成0101这一类的硬件指令。横向对比其他语言,比如C语言变成机器码的过程如下

虽然CPU能读懂0101这一类指令,但是每一个CPU的型号都不一样,不同的 CPU 能够执行的指令不一样,因此也就有指令集之分。即使排除CPU的因素,不同操作系统运行的可执行文件的格式又不一样。上面的流程图更加细化的流程如下:

汇编译成的汇编代码我们称作目标代码,而真正的可以被CPU执行的其实是可执行文件

可执行文件是由多个目标文件通过连接器链接而成

接着由加载器加载到内存中然后CPU才开始从内存获取指令或者数据进行处理。可执行文件根据操作系统的不同格式也不同,比如 Linux 系统连接器生成的可执行文件是ELF格式的,而 Windows 下的可执行文件是PE格式的。不同的操作系统的加载器解析的文件格式又不一样,因此即使是指令集相同的同一款CPU,C语言也无法在Windows编译后在Linux下运行。

再回来反观Java一直宣传的口号:一次编译到处运行。那么它是怎么做到在Windows下编译然后在Linux平台下运行的呢?

通过上面C语言的例子,我们主要解决两个问题就能让C语言也跨平台:

  1. 不同平台的加载器能够加载不同格式的可执行文件

  2. CPU指令集统一起来,不同CPU的指令集统一成同一套

上面两个问题看似好解决,但现实中各种商业因素或者技术问题无法兼容等原因使得变得很困难(要不然AMD和因特尔就可以一家人整整齐齐了)

Java的JVM使得这高级语言跨平台这一切变得有可能:

Java代码编译成的.class文件被称为字节码文件。里面的内容是一些类似汇编语言的指令。我们可以通过javap命令查看这些字节码文件。

编写Java代码

public class User  {
    private Integer id;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
}

运行 javap -c User.class

生成的类汇编指令为

Compiled from "User.java"
public class com.ethan.entity.User implements java.io.Serializable {
  public com.ethan.entity.User();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public java.lang.Integer getId();
    Code:
       0: aload_0
       1: getfield      #2                  // Field id:Ljava/lang/Integer;
       4: areturn

  public void setId(java.lang.Integer);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #2                  // Field id:Ljava/lang/Integer;
       5: return
}

里面的指令代码就不展开讲解了,贴出来的目的只是想要说明Java把C语言中编译成汇编语言这一步操作放在了虚拟机中实现,并生成了类似汇编语言的指令。

接着JVM又会将这些指令像汇编语言一一对应成机器码一样,也翻译成了当前运行的机器能够理解的机器码。这样就完成了跨平台的操作。因此理论上只要任何高级语言编译成.class文件都能在JVM中运行并且实现跨平台。

对于JVM内部的一些细节像执行模式,装载机制等就不展开来讲了,本篇的目的在于解释Java程序与底层硬件的交互。看到这里,你可能也会好奇Java有JVM实现了跨平台,那JVM呢?它是怎么实现跨平台的?为什么我在Windows下运行JVM也可以在Linux下运行也可以,答案是:那是因为你下载了不同操作系统的JVM。JVM是不跨平台的,它本身也只是一个应用程序也是依赖于不同操作系统,不同的系统使用的虚拟机不同。JVM使得在运行的时候操作系统和底层硬件对于Java程序来说是透明的。

总结:通过C语言与Java语言的对比更加体会到了JVM帮我们做了那么多底层交互的工作。后面C#跨平台的.net core参考JVM的。最后在强调一点,Java程序跨平台,JVM不跨平台。


[参考]:徐文浩 极客时间 [出计算机组成原理]


文章转载自一十二章经,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论