Java использует байт-код, чтобы быть независимым от платформы и «предварительно скомпилированным», но байт-код используется интерпретатором и подается достаточно компактным, так что это не тот машинный код, который вы можете видеть в скомпилированной программе на Си. Просто взгляните на весь процесс компиляции 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-программы в машинный код. Как видите, байт-код находится далеко от машинного кода. Я не могу найти в Интернете хороших вещей, чтобы показать вам этот путь в реальной программе (пример), все, что я нашел, это эта презентация , здесь вы можете увидеть, как каждый шаг меняет представление кода. Я надеюсь, что это ответит вам, как и почему скомпилированная программа c и байт-код Java отличаются.
UPDATE:
Все этапы после «байт-кода» выполняются JVM во время выполнения в зависимости от его решения скомпилировать этот код (это другая история ... JVM балансирует между интерпретацией байт-кода и его компиляцией в собственный код, зависящий от платформы)
Наконец нашел хороший пример, взятый из Распределение регистров линейного сканирования для клиентского компилятора 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. Я не принимаю во внимание ссылки (упоминавшиеся другими), а также заголовки файлов compiledc и bytecode (возможно, они тоже разные; я не знаю, как с c, но в файле bytecode все строки перемещаются в специальный пул заголовков, и в программе используется его «позиция» в заголовке и т. д.)
UPDATE2: Вероятно, стоит упомянуть, что java работает со стеком (команды istore / iload), хотя машинный код, основанный на x86 и большинстве других платформ, работает с регистрами. Как вы можете видеть, машинный код «полон» регистров, и это дает дополнительный размер скомпилированной программе по сравнению с более простым стековым байт-кодом.