为什么编译的Java类文件比C的编译文件小?


问题内容

我想知道为什么我们通过编译显示“ Hello,World!”的.c文件得到.o文件。是否大于Java .class文件,该文件也显示“
Hello,World!”?


问题答案:

Java使用字节码来独立于平台并进行“预编译”,但是字节码由解释器使用并且被提供为足够紧凑,因此您在已编译的C程序中看到的机器代码并不相同。只需看一下Java编译的完整过程即可:

Java program  
-> Bytecode   
  -> High-level Intermediate Representation (HIR)   
    -> Middle-level Intermediate Representation (MIR)   
      -> Low-level Intermediate Representation (LIR)  
        -> Register allocation
          -> EMIT (Machine Code)

这是Java程序到机器代码转换的链。如您所见,字节码与机器代码相距甚远。我在Internet上找不到能在实际程序上向您展示这条路的东西(一个示例),我发现的只是此演示文稿,在这里您可以看到每个步骤如何更改代码演示文稿。我希望它能回答您如何以及为何编译的c程序和Java字节码不同。

更新:
“字节码”之后的所有步骤都由JVM在运行时完成,这取决于其编译该代码的决定(这是另一回事了……JVM在字节码解释和其编译成与本地平台相关的代码之间取得平衡)

最终找到了一个很好的例子,摘自Java
HotSpot™客户端编译器的线性扫描寄存器分配
(很好地理解了JVM内部的情况)。想象一下,我们有Java程序:

public static void fibonacci() {
  int lo = 0;
  int hi = 1;
  while (hi < 10000) {
    hi = hi + lo;
    lo = hi - lo;
    print(lo);
  }
}

那么它的字节码是:

0:  iconst_0
1:  istore_0 // lo = 0
2:  iconst_1
3:  istore_1 // hi = 1
4:  iload_1
5:  sipush 10000
8:  if_icmpge 26 // while (hi < 10000)
11: iload_1
12: iload_0
13: iadd
14: istore_1 // hi = hi + lo
15: iload_1
16: iload_0
17: isub
18: istore_0 // lo = hi - lo
19: iload_0
20: invokestatic #12 // print(lo)
23: goto 4 // end of while-loop
26: return

每个命令占用1个字节(JVM支持256个命令,但实际上少于该数目)+参数。总共需要27个字节。我省略了所有阶段,现在可以执行机器代码了:

00000000: mov dword ptr [esp-3000h], eax
00000007: push ebp
00000008: mov ebp, esp
0000000a: sub esp, 18h
0000000d: mov esi, 1h
00000012: mov edi, 0h
00000017: nop
00000018: cmp esi, 2710h
0000001e: jge 00000049
00000024: add esi, edi
00000026: mov ebx, esi
00000028: sub ebx, edi
0000002a: mov dword ptr [esp], ebx
0000002d: mov dword ptr [ebp-8h], ebx
00000030: mov dword ptr [ebp-4h], esi
00000033: call 00a50d40
00000038: mov esi, dword ptr [ebp-4h]
0000003b: mov edi, dword ptr [ebp-8h]
0000003e: test dword ptr [370000h], eax
00000044: jmp 00000018
00000049: mov esp, ebp
0000004b: pop ebp
0000004c: test dword ptr [370000h], eax
00000052: ret

结果需要83个字节(十六进制52个+ 1字节)。

PS。我没有考虑链接(其他人提到过),也没有考虑编译的c和字节码文件头(可能也有所不同;我不知道c怎么回事,但是在字节码文件中,所有字符串都移到了特殊的标头池,并且在程序中在标头等处使用其“位置”。)

UPDATE2: 也许值得一提的是,尽管基于x86和大多数其他平台的机器代码可以与寄存器一起使用,但是java与堆栈(istore /
iload命令)一起使用。如您所见,机器代码充满了寄存器,与更简单的基于堆栈的字节码相比,它为编译的程序提供了更大的空间。