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

Ahm*_*boh 12 c java bytecode executable-format

我想知道为什么我们从编译.c文件得到的.o文件打印出"Hello,World!" 大于Java .class文件,也打印"Hello,World!"?

Max*_*xym 13

Java使用Bytecode是独立于平台并且是"预编译"的,但字节码由解释器使用并且足够紧凑,因此它与您在编译的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)
Run Code Online (Sandbox Code Playgroud)

这是Java程序到机器代码转换的链.如您所见,字节码远离机器代码.我在互联网上找不到好东西向你展示真实程序的这条路(一个例子),我发现的一切都是这个演示文稿,在这里你可以看到每个步骤如何改变代码的呈现方式.我希望它能回答你如何以及为什么编译的c程序和Java字节码是不同的.

更新: "字节码"之后的所有步骤都是由JVM在运行时完成的,这取决于它编译代码的决定(这是另一个故事...... JVM在字节码解释和编译到本机平台相关代码之间进行平衡)

最后找到了一个很好的例子,取自Java HotSpot™客户端编译器的线性扫描寄存器分配(btw良好的阅读,以了解JVM内部发生了什么).想象一下,我们有java程序:

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

然后它的字节码是:

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
Run Code Online (Sandbox Code Playgroud)

每个命令需要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
Run Code Online (Sandbox Code Playgroud)

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

PS.我没有考虑链接(被别人提到),以及compilec和字节码文件头(可能它们也不同;我不知道它是如何与c,但在字节码文件中所有字符串都移动到特殊的标题池,并在程序中使用其标题中的"位置"等)

UPDATE2:可能值得一提的是,java使用stack(istore/iload命令),虽然基于x86和大多数其他平台的机器代码与寄存器一起工作.正如您所看到的,机器代码是"完整"的寄存器,与更简单的基于堆栈的字节码相比,它为编译的程序提供了额外的大小.


axt*_*avt 7

在这种情况下,尺寸差异的主要原因是文件格式的差异.对于如此小的程序格式的ELF(.o)文件在空间方面引入了严重的开销.

例如,我.o的"Hello,world"程序的示例文件占用864个字节.它由(用readelf命令探索)组成:

  • 52个字节的文件头
  • 440字节的节头(40字节x 11节)
  • 81个字节的节名
  • 160字节的符号表
  • 43个字节的代码
  • 14个字节的数据(Hello, world\n\0)
  • 等等

.class类似程序的文件只占用415个字节,尽管它包含更多的符号名称并且这些名称很长.它由(使用Java Class Viewer进行探索)组成:

  • 289个字节的常量池(包括常量,符号名称等)
  • 94字节的方法表(代码)
  • 8个字节的属性表(源文件名参考)
  • 24字节的固定大小的标头

也可以看看: