Почему скомпилированные файлы классов Java меньше C скомпилированных файлов?

Я хотел бы знать, почему файл .o, который мы получаем от компиляции файла .c, который печатает "Hello, World!" больше, чем файл Java.class, который также печатает "Hello, World!"?

Ответы

Ответ 1

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)

это цепочка для преобразования кода программы Java в машину. Как вы видите, байт-код находится далеко от машинного кода. Я не могу найти в Интернете хорошие вещи, чтобы показать вам эту дорогу на реальной программе (пример), все, что я нашел, эта презентация, здесь вы можете увидеть, как каждый шаг изменяет представление кода. Надеюсь, он ответит вам, как и почему скомпилированные c-программы и Java-байт-коды различаются.

UPDATE: Все шаги, которые выполняются после "байт-кода", выполняются 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);
  }
}

то его байт-код:

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, но в файле байт-кода все строки перемещаются в специальный пул заголовков, а в программе используется его "позиция" в заголовке и т.д.)

UPDATE2: Возможно, стоит упомянуть, что java работает с командами stack (istore/iload), хотя машинный код на основе x86 и большинство других платформ работает с регистрами. Как вы видите, машинный код является "полным" регистров и дает дополнительный размер скомпилированной программе в сравнении с более простым байт-кодом на основе стека.

Ответ 2

Основной причиной разницы в размере в этом случае является различие в форматах файлов. Для такого небольшого формата программы 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 байта заголовков фиксированного размера

См. также:

Ответ 3

Программы

C, даже если они скомпилированы в собственный машинный код, который работает на вашем процессоре (естественно, отправляется через ОС), как правило, нужно много настраивать и срывать для операционной системы, загружать динамически связанные библиотеки, такие как библиотека C и т.д.

Java, с другой стороны, компилирует байт-код для виртуальной платформы (в основном моделируемый компьютер в составе компьютера), который специально разработан вместе с самой Java, поэтому многие из этих накладных расходов (если бы это было необходимо, так как и код, и интерфейс виртуальной машины хорошо определены) могут быть перемещены в самую виртуальную виртуальную машину, оставив программный код худым.

Он варьируется от компилятора к компилятору, но есть несколько вариантов его уменьшения или создания кода по-разному, что будет иметь разные эффекты.

Все это сказало, это не очень важно.

Ответ 4

Вкратце: Java-программы скомпилированы в байт-код Java, что требует выполнения отдельного интерпретатора (Java Virtual Machine).

Нет гарантии 100%, что файл .o, созданный c-компилятором, меньше, чем файл .class, созданный компилятором Java. Все зависит от реализации компилятора.

Ответ 5

Одной из основных причин различий в размерах файлов .o и .class является то, что байт-коды Java немного выше, чем машинные инструкции. Разумеется, не слишком высокий уровень - это все еще довольно низкоуровневый материал, но это будет иметь значение, потому что оно эффективно действует на сжатие всей программы. (Оба кода C и Java могут иметь код запуска.)

Другое отличие состоит в том, что файлы классов Java часто представляют собой относительно небольшие функциональные возможности. Несмотря на то, что возможно иметь объектные файлы C, которые сопоставляются с еще меньшими частями, часто чаще распространять больше (связанных) функциональных возможностей в одном файле. Различия в правилах видимости также могут акцентировать внимание на этом (у C действительно нет ничего, что соответствует областью уровня модуля, но оно имеет область уровня файла, а область видимости пакета Java работает с несколькими файлами класса). Если вы сравните размер всей программы, вы получите лучшую метрику.

В терминах "связанных" размеров исполняемые JAR файлы Java имеют тенденцию быть меньше (для определенного уровня функциональности), потому что они поставляются сжатыми. Это относительно редко для доставки программ C в сжатой форме. (Там также различия в размере стандартной библиотеки, но они также могут быть стиранием, потому что программы на C могут рассчитывать на библиотеки, отличные от libc, и программы Java имеют доступ к огромной стандартной библиотеке. неудобно.)

Затем возникает вопрос об отладочной информации. В частности, если вы скомпилируете C-программу с отладкой на IO, вы получите много информации о типах в стандартной библиотеке, только потому, что она слишком сложна, чтобы отфильтровать ее. Код Java будет иметь только отладочную информацию о фактическом скомпилированном коде, поскольку он может рассчитывать на доступную информацию в объектном файле. Изменяет ли этот размер фактический размер кода? Нет. Но это может сильно повлиять на размеры файлов.

В целом, я бы предположил, что трудно сравнивать размеры программ на C и Java. Вернее, вы можете сравнить их и легко узнать ничего полезного.

Ответ 6

Большинство (до 90% для простых функций) файла ELF-формата .o являются нежелательными. Для файла .o, содержащего единственное пустое тело функции, вы можете ожидать разбивку по размеру, например:

  • код 1%
  • Таблица символов 9% и перемещение (необходимое для связывания)
  • Накладные расходы на 90%, бесполезные версии/вендоры, хранящиеся в компиляторе и/или ассемблере и т.д.

Если вы хотите увидеть реальный размер скомпилированного кода C, используйте команду size.

Ответ 7

Файл класса - это байт-код Java.

Это, скорее всего, меньше, поскольку библиотеки C/С++ и библиотеки операционной системы связаны с объектным кодом, который производит компилятор С++, чтобы, наконец, сделать исполняемый двоичный файл.

Проще говоря, это похоже на сравнение Java-байтового кода с объектным кодом, созданным компилятором C, прежде чем он будет связан с созданием двоичного файла. Разница заключается в том, что JVM интерпретирует байт-код Java, чтобы правильно выполнять то, что программа предназначена для выполнения, тогда как C требует информации из операционной системы, поскольку операционная система функционирует как интерпретатор.

Также в C Каждый символ (функции и т.д.) вы ссылаетесь на внешнюю библиотеку, по крайней мере, один раз в одном из объектных файлов. Если вы используете его в нескольких объектных файлах, он все равно импортируется только один раз. Это может быть два способа импорта. При статической привязке фактический код функции копируется в исполняемый файл. Это увеличивает размер файла, но имеет то преимущество, что внешние библиотеки (файлы .dll/.so) не нужны. С динамическим связыванием это не происходит, но в результате ваша программа требует запуска дополнительных библиотек.

В Java все так "связано" динамически, так сказать.

Ответ 8

Java компилируется в машинный независимый язык. Это означает, что после его компиляции он затем транслируется во время выполнения виртуальной машиной Java (JVM). C скомпилирован для машинных инструкций и, следовательно, является всем двоичным кодом для запуска программы на целевой машине.

Поскольку Java скомпилирован на независимый от машины язык, конкретные детали для конкретной машины обрабатываются JVM. (то есть C имеет служебные данные, специфичные для машины)

Вот как я об этом думаю: -)

Ответ 9

Несколько потенциальных причин:

  • Файл класса Java не включает код инициализации вообще. У этого есть только один класс и одна функция в нем - очень мало. Для сравнения, программа C имеет некоторую степень статически связанного кода инициализации и, возможно, DLL-thunks.
  • Программа C также может иметь разделы, выровненные по границам страниц - это добавит минимум 4kb к размеру программы точно так же, чтобы обеспечить, чтобы сегмент кода начинался с границы страницы.