深入探索JVM垃圾回收:ARM服务器垃圾回收的挑战和优化
上QQ阅读APP看书,第一时间看更新

1.2.1 从源代码到目标代码

一个简单的C示例如下:

int global_count = 10;
int add(int i, int j){
    return i + j;
}
main(){
    int i = 3;
    int j = 5;
    int result = add(i, j);
}

该示例非常简单,不存在动态链接,编译、链接完成后即可在OS中执行。但是程序要在OS上执行,需要符合OS的执行要求,主要包括:

  • 产生的可执行文件必须符合格式规范(如Linux中必须符合ELF格式)。
  • 可执行文件中内容的组织符合OS执行程序的约定规范,例如程序在执行时由数据段(data segment)、代码段(text segment)等组成。

以Linux系统为例,上面的源代码和编译生成的可执行文件(ELF格式)的对应关系如下图1-3所示。

图1-3 代码和ELF格式约定

以Linux/X86-64为例,通过gcc编译器对上述代码进行编译,产生目标文件。文件格式为ELF,可以使用objdump命令(或readelf命令)对编译后的目标文件进行解析。首先可以确认一下数据段的信息,如下所示:

Disassembly of section .data:
0000000000600868 <__data_start>:
  600868:       00 00                   add    %al,(%rax)
        ……
000000000060086c <global_count>:
  60086c:       0a 00                   or     (%rax),%al

在这个数据段中有两个变量__data_start和global_count,其中global_count是代码中定义的全局变量,可以看到该变量占用的空间为4字节,初始值为10;而__data_start是gcc在链接时创建的一个全局变量,该变量指向数据段开始的位置,该变量的大小也是4字节。

编译器除了满足OS对于可执行文件的约定规范外,其中一个重要的功能就是针对代码进行编译优化(当然也包含了内存数据的布局等)。接下来看一下代码段的内容。代码段非常长,这里只关注add函数的汇编代码,如下所示:

0000000000400474 <add>:
  400474:     55            push        %rbp
  400475:     48 89 e5      mov         %rsp,%rbp
  400478:     89 7d fc      mov         %edi,-0x4(%rbp)
  40047b:     89 75 f8      mov         %esi,-0x8(%rbp)
  40047e:     8b 45 f8      mov         -0x8(%rbp),%eax
  400481:     8b 55 fc      mov         -0x4(%rbp),%edx
  400484:     8d 04 02      lea         (%rdx,%rax,1),%eax
  400487:     c9            leaveq
  400488:     c3            retq

注意

在gcc编译过程中采用的是默认编译优化级别(默认编译优化级别为O0),如果采用不同的编译优化级别,生成的代码会略有不同。

在C/C++中,编译优化体现在源代码的编译时间长短不同,同时不同的编译代码执行效率也会不同。在JVM的执行过程中也存在同样的问题,并且因为JVM在编译代码执行过程中需要先等待编译代码完成后才能执行,所以编译时长会直接影响应用执行的性能。