本文将从高级语言与机器指令的差异讲起,聊聊 Java 技术体系中,程序的构造与执行原理。目标是能勾勒出一条知识路线,认识日常繁杂的业务代码运行背后那些默默支持的底层技术。文章中部分章节为读书笔记,其原理性的描述参考的书籍,放在了文章末尾处。
我们先从一段 C 语言代码,通过编译、反编译工具认识一下高级语言与机器指令的差异。为后续 Java 程序的组织结构做一个铺垫。
- int accum = 0;
- int sum(int x,int y)
- {
- int t = x + y;
- accum += t;
- return t;
- }
通过 gcc -O1 -c code.c 编译汇编之后:生成 code.o 二进制文件,其以 16 进制打开后展示为:
- cffa edfe 0700 0001 0300 0000 0100 0000
- 0400 0000 0802 0000 0020 0000 0000 0000
- 1900 0000 8801 0000 0000 0000 0000 0000
- 0000 0000 0000 0000 0000 0000 0000 0000
- 7400 0000 0000 0000 2802 0000 0000 0000
- ...
本次编译环境为:Intel Core I5 8500B,MacOS 10.15.7,Apple clang version 12.0.0 (clang-1200.0.32.28)
通过反汇编工具,将 code.o 逆向出来则其汇编代码如下:
- objdump -d code.o
- code.o: file format Mach-O 64-bit x86-64
- Disassembly of section __TEXT,__text:
- 0000000000000000 _sum:
- 0: 55 pushq %rbp
- 1: 48 89 e5 movq %rsp, %rbp
- 4: 89 f8 movl %edi, %eax
- 6: 01 f0 addl %esi, %eax
- 8: 01 05 00 00 00 00 addl %eax, (%rip)
- e: 5d popq %rbp
- f: c3 r
以上,代码展示了一个简单的 C 程序形态、二进制程序文件、汇编指令,从汇编指令可以看到具体编译环境所对应的指令格式与寄存器名称。这种基于寄存器设计的指令格式,与 Java 面向栈的指令结构存在较大差异。后边我们将会讲解。
再让我们回顾一下计算机系统相关的一些原理,这对后边理解 Java 虚拟机的原理有很大帮助,Java 虚拟机在设计上借鉴了很多 C 程序设计思路。因此存在很多相似之处。
预处理阶段:修改原始 C 程序,根据以 #开头的命令插入相应内容,比如 #include
编译器阶段:将.i 文本文件翻译成汇编文件.s 汇编语言程序中的每条语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。汇编语言为不同高级语言编译器提供了通用的输出语言。比如 C 编译器和 Fortran 编译器产生的输出文件用的都是同样的汇编语言。
汇编阶段:汇编器(as)将 xxx.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式并将结果保存在 xxx.o 中。
连接阶段:合并函数调用,汇编阶段产生的仅包含目标函数的“符号引用”。链接就是将对应的函数进行合并,结果就得到可执行目标文件,可被加载到内存中,由系统执行。
该图展示了计算机系统的典型组织原理架构图。其包含了处理器、IO 系统、存储器(内存)、磁盘、输入输出设备以及其它外设。
该图展示了指令格式在概念上的形态,从第一部分我们可以看到,C 语言编译汇编后的指令格式仅有两个地址,目的地址与源地址。
指令寻址方式有:直接寻址,间接寻址,绝对寻址,寄存器寻址,立即数寻址,变址寻址,比例变址寻址。
IA32 整数寄存器文件包含 8 个命名位置,分别存储 32 位的值。这些寄存器可以存储地址(C 的指针)或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他的寄存器则用来保存临时数据,例如程序运行过程的局部变量和函数返回值。
%ax,%cx,%dx,%bx 这些是对 16 位操作,%ah,%al...这是对字节(8 位)操作
下图展示了 JavaSE 相关的技术体系,从图中可以看到,以最上层的 Java 语言,以及下部的 Java 虚拟机两大技术为基础,构成了 Java 技术体系。
JVM 所处的位置
JVM 是用 C++构建的应用,它是沟通不同操作系统的桥梁,也是实现跨平台的关技术组件。
Java 的跨平台与 C/C++的区别:Java 一处编译到处运行;C/C++则需要在不同的平台上多次编译。
处理器指令集不止一种、操作系统不止一种,如果要实现一处编译到处运行,则需要一个中间技术解决这个难题。Java 除了跨平台以外,还帮程序员解决了内存回收管理问题,降低了软件开发难度。