Об управлении памятью в Java и C ++ - PullRequest
8 голосов
/ 14 января 2009

Ну, мне дали задание, чтобы понять, как распределять память для любого языка, который я буду использовать. После некоторого исследования у меня есть несколько вопросов и сомнений, в которых я хотел бы получить некоторое представление. Например:

Я прочитал здесь , что Java определяет, как именно организовано содержимое стека. Глядя на структуру спецификации JVM , он в основном говорит, что стек содержит фреймы, и что фреймы содержат все, что находится внутри класса, путем правильного распределения переменных и функций. Может быть, я что-то здесь упускаю, но я не понимаю, чем это отличается от того, что делает C ++. Я спрашиваю, потому что первая ссылка говорит, что спецификация содержимого стека Java избегает несовместимости компилятора.

Кроме того, мне еще предстоит выяснить, как сегменты памяти точно расположены друг над другом. Например, я знаю, что память делится на глобальные переменные, стек вызовов, кучу и код для C ++, но я не знаю, является ли адрес кучи выше, чем стек, или зависит ли это от реализации. Мне также интересно, есть ли в Java-программе нечто большее, и как это будет выложено. Я предполагаю, что есть стандарт, так как JVM должна знать, где все это использовать, хотя я предполагаю, что он мог бы просто иметь указатели и оставить остальное для ОС. Я тоже представляю, что должен быть хотя бы де-факто стандарт.

Еще одна вещь, которую я не понимаю, это пул констант времени выполнения. Предполагается, что это «представление времени выполнения для класса или таблицы для таблицы constant_pool в файле класса», но я не думаю, что понимаю, что она делает. Кажется, есть тег для обозначения типа рассматриваемой структуры? Затем имя структуры (данное программистом или назначенное базовой системой?). Затем кажется, что остальная часть меняется в зависимости от того, что описывает тег (поток, массив и т. Д.).

Если моя интерпретация пула констант времени выполнения верна, то почему они так же необходимы, как и стековые фреймы? Это потому, что кадры стека заботятся только о сегментах стека, а пул констант времени выполнения должен также иметь указатели для выделенной памяти кучи?

Ответы [ 2 ]

6 голосов
/ 14 января 2009

Глядя на структуру спецификации JVM, он в основном говорит, что стек содержит фреймы, и что фреймы содержат все, что находится внутри класса, путем правильного распределения переменных и функций. Может быть, я что-то здесь упускаю, но я не понимаю, чем это отличается от того, что делает C ++. Я спрашиваю, потому что первая ссылка говорит, что спецификация содержимого стека Java избегает несовместимости компилятора.

На практике компиляторы C ++ следуют той же базовой стратегии. Однако комитет по стандартам не считает это проблемой языка. Вместо этого компиляторы C ++ следуют этой системе, потому что так устроены большинство процессоров и операционных систем. Различные платформы расходятся во мнениях относительно того, передаются ли данные функциям в стеке или через регистры (машины RISC), растет ли стек или уменьшается, существуют ли разные соглашения о вызовах, позволяющие «обычным» вызовам использовать стек, а другим - использовать somethign иначе (например, __ fastcall и naked ), существует ли такая вещь, как вложенные функции , поддержка хвостовых вызовов и т. д.

Фактически, соответствующий компилятор C ++ может скомпилировать что-то вроде виртуальной машины Scheme, где «стек» сильно отличается, поскольку Scheme требует реализаций для поддержки как хвостовых вызовов, так и продолжений. Я никогда не видел ничего подобного, но это было бы законно.

«Несовместимость компилятора» наиболее очевидна, если вы попытаетесь написать сборщик мусора :

все локальные переменные, как для текущей функции, так и для всех ее вызывающих, находятся в ["" стеке, но рассмотрим ucontext.h и Windows Fibers ]. Для каждой платформы (то есть OS + CPU + компилятор) есть способ узнать, где находится ["стек"]. Тамарин делает это, затем сканирует всю эту память во время GC, чтобы увидеть, на что указывают местные жители. ...

Эта магия живет в макросе MMGC_GET_STACK_EXTENTS, определенном в заголовке MMgc / GC.h. ... [T] вот отдельная реализация для каждой платформы.

В любой момент времени некоторые локальные объекты могут находиться в регистрах ЦП, а не в стеке. Чтобы справиться с этим, макрос использует несколько строк кода сборки, чтобы выгрузить содержимое всех регистров в стек. Таким образом, MMgc может просто сканировать стек и видеть все локальные переменные.


Кроме того, объекты в Java обычно не выделяются в стеке. Вместо этого ссылки на них есть. Типы int, double, boolean и другие примитивы распределяются в стеке. В C ++ все может быть размещено в стеке, который имеет свой список плюсов и минусов.

Еще одна вещь, которую я не понимаю, это пул констант времени выполнения. Предполагается, что это «представление времени выполнения для класса или таблицы для таблицы constant_pool в файле класса», но я не думаю, что понимаю, что она делает.

Рассмотрим:

String s = "Hello World";
int i = "Hello World".length();
int j = 5;

s, i и j - все переменные, и каждая из них может быть изменена на более позднем этапе программы. Однако «Hello World» - это объект типа String, который нельзя изменить, 5 - это int, который нельзя изменить, и «Hello World» .length () можно определить во время компиляции, чтобы всегда возвращать 11. Эти константы допустимые объекты и методы могут быть вызваны для них (ну, по крайней мере, для String), поэтому их нужно где-то размещать. Но они не могут быть изменены, никогда. Если эти константы принадлежат классу, они размещаются в пуле констант для каждого класса. Другие константные данные, которые не являются частью класса (например, идентификатор потока main ()), размещаются в пуле констант для каждой среды выполнения (в данном случае «среда выполнения» означает «экземпляр JVM»).

Стандарт C ++ имеет некоторый язык о подобной технике, но реализация оставлена ​​на усмотрение двоичного формата (ELF, a.out, COFF, PE и т. Д.). Стандарт ожидает, что константы, являющиеся целочисленными типами данных (bool, int, long и т. Д.) Или строки в стиле c, будут фактически храниться в постоянной части двоичного файла, тогда как другие константные данные (double, float, классы) могут храниться как переменная вместе с флагом, говорящим о том, что «переменная» не является изменяемой (также допустимо хранить их с целочисленными и строковыми константами в стиле c, но многие двоичные форматы не делают это опцией).

Вообще говоря, «секция постоянных данных» двоичного файла может использоваться совместно, если одновременно открыто более одной копии программы (поскольку постоянные данные будут идентичны в каждой копии программы). На ELF этот раздел называется разделом .rodata .

3 голосов
/ 14 января 2009

Какую именно задачу вам дали?

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

Что касается стека, фрейм является просто более "официальной" и стандартной формой того, что делают компиляторы C ++. Компиляторы C ++ просто помещают вещи друг на друга в стек при переходе от вызова к вызову. В Java этот термин является фреймом, и поскольку предполагается, что скомпилированный код Java работает на любой платформе, существуют очень четкие стандарты того, как это происходит. В C ++ каждый компилятор может обрабатывать стек по-разному (например, даже в зависимости от размера слова).

В Java все работает внутри виртуальной машины, которая управляет всем, хотя и делегирует некоторые вещи в среду. Другими словами, у вас нет доступа к тому, куда JVM помещает ваши данные и ваш код, и ваш код может даже никогда не стать настоящим «сегментом кода». Другими словами, на это не может быть ответа. В C ++ все работает на оборудовании, поэтому у вас будут сегменты стека, сегменты данных и т. Д. Посмотрите информацию о C ++.

В C ++ классы не имеют представления в памяти во время выполнения; фактически вы можете скомпилировать C ++ в C, а затем скомпилировать результаты в сборку. В Java все также представлено во время выполнения, поэтому вы можете спросить объект, к какому классу он принадлежит и какой метод поддерживает. Следовательно, каждый файл класса имеет «постоянный пул», в котором появляются строки, представляющие такие вещи, как имена методов, имена полей и т. Д. Фактическое определение класса относится к пулу. Другими словами, это очень мало связано с кадрами стека. В кадрах стека хранятся параметры метода, локальные переменные и возвращаемые значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...