Оригинал|Интервьюер: Обязательно ли объекты Java размещаются в куче?
Недавно я просматривал информацию о виртуальной машине Java, готовясь к чрезвычайным ситуациям на работе. Во-первых, я задаю вопрос для интервью, о котором думаю сам, а затем ввожу знания, которые будут введены позже, такие как escape-анализ, скалярная замена, распределение стека и т. д.
вопросы интервью
Обязательно ли объекты Java размещаются в куче?
Сначала подумайте сами, а затем читайте дальше, чтобы получить лучшие результаты!
анализировать
Все мы знаем, что объекты Java обычно располагаются в куче, а пространство кучи совместно используется всеми потоками. Друзья, знакомые с библиотекой NIO, должны знать, что существует еще один тип памяти вне кучи, также называемый прямой памятью. Прямая память — это область памяти, непосредственно применяемая к операционной системе, и скорость доступа к прямой памяти обычно лучше, чем кучи. Размер прямой памяти напрямую не ограничен значением, установленным Xmx, но при его использовании следует обращать внимание, ведь системная память ограничена, и сумма памяти кучи и прямой памяти все равно будет ограничена памятью операционной системы.
Благодаря приведенному выше анализу мы все знаем, что объекты Java могут размещаться не только в куче, но и непосредственно в памяти вне кучи. Но сегодня я хочу обсудить не это, я хочу поговорить с вами о выделении памяти в стеке, а когда дело доходит до выделения памяти в стеке, я должен сначала поговорить об этом.анализ побега
анализ побега
Escape-анализ — это статический анализ, который динамически определяет динамический диапазон указателя, который может анализировать, где в программе можно получить доступ к указателю.
другими словами,Цель escape-анализа состоит в том, чтобы определить, может ли область действия объекта выйти за пределы тела метода.
Суждение основано на двух
- Хранится ли объект в куче (статические поля или поля экземпляров объектов в куче)
- Был ли объект передан в неизвестный код (вызывающий метод и параметры)
Давайте проанализируем эти два
Для первого пункта, хранится ли объект в куче, мы знаем, что память кучи совместно используется потоками.Как только объект размещен в куче, все потоки могут получить доступ к объекту, так что компилятор в реальном времени не может отслеживать все используемые объекты.Место объекта, такой объект принадлежит объекту побега, как показано ниже
public class Escape {
private static User u;
public static void alloc() {
u = new User(1, "baiya");
}
}
Пользовательский объект принадлежит переменной-члену класса Escape, к этому объекту могут обращаться все потоки, поэтому произойдет побег.
Второй момент заключается в том, передается ли объект в неизвестный код. JIT-компилятор Java компилирует по методу. JIT-компилятор будет рассматривать метод, который не встроен, как неизвестный код, поэтому невозможно судить о неизвестный метод. Вызов метода не помещает вызывающую программу или параметры в кучу, поэтому вызывающая программа и параметры метода считаются экранированными, как показано ниже.
public class Escape {
private static User u;
public static void alloc(User user) {
u = user;
}
}
Параметр user метода alloc присваивается переменной-члену u класса Escape, поэтому к нему также будут обращаться все потоки, и также произойдет escape.
выделение стека
Распределение стека — это технология оптимизации, предоставляемая виртуальной машиной Java.Основная идея этой технологии состоит в том, чтобы разбить объекты, принадлежащие потоку, и разместить их в стеке, а не в куче. В чем преимущество размещения в стеке? Мы знаем, что переменные в стеке будут автоматически уничтожены после завершения вызова метода, поэтому сборка мусора jvm опущена, что может повысить производительность системы.
распределение стека основано наанализ побегаа такжескалярная заменаосуществленный
Мы используем конкретный пример, чтобы убедиться, что объект анализа без экранирования действительно размещен в стеке.
public class OnStack {
public static void alloc() {
User user = new User(1, "baiya");
}
public static void main(String[] args) {
long start = Instant.now().toEpochMilli();
for (int i = 0; i < 100_000_000; i++) {
alloc();
}
long end = Instant.now().toEpochMilli();
System.out.println("耗时:" + (end - start));
}
}
Вышеприведенный код зацикливается 100 миллионов раз для выполнения метода alloc для создания объекта User, каждый объект User занимает около 16 байт (как его вычислить, будет описано ниже) пространства и создается 100 миллионов раз, поэтому, если объект User выделяется в куче, для этого должно быть 1,5 ГБ памяти. Если мы установим размер кучи меньше этого числа, должна произойти сборка мусора, если она установлена очень маленькой, должно произойти много операций сборки мусора.
Мы выполняем приведенный выше код со следующими параметрами
-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+EliminateAllocations
Где -server для включения режима сервера, анализ побега требует поддержки режима сервера.
-Xmx10 -Xms10m, установить динамическую память на 10 м, намного меньше 1,5 Гб.
-XX:+DoEscapeAnalysis Включить анализ выхода
-XX:+PrintGCDetails Если происходит gc, распечатать журнал gc
-XX:+EliminateAllocations включает скалярную замену, позволяя разбрасывать и размещать объекты в стеке, например объект User, который имеет два атрибута id и name, которые можно рассматривать как независимые локальные переменные и выделять отдельно
После настройки параметров jvm выполните код и проверьте результаты, чтобы увидеть, что gc выполняется 3 раза, что занимает миллисекунды 10. Можно сделать вывод, что объекты User не все размещены в куче, но большинство из них выделенный в стек.Преимущество выделения в стеке заключается в том, что соответствующая память автоматически освобождается после завершения метода., это метод оптимизации.
Мы упоминали выше, что выделение стека зависит от escape-анализа и скалярной замены, тогда мы можем разрушить любое из этих условий, удалить escape-анализ, а затем выполнить приведенный выше код через -XX:-DoEscapteAnalysis или отключить скалярную замену -XX: -EliminateAllocations, наблюдая за ситуацией с выполнением, обнаружил, что произошло много gc, и это заняло 3182 миллисекунды, а время выполнения было намного выше, чем указанные выше 10 миллисекунд, поэтому можно предположить, что метод оптимизации распределения стека не был выполнен.
Рассчитать размер пространства, занимаемого объектом User
Объект состоит из четырех частей
Заголовок объекта: запишите имя экземпляра, идентификатор и состояние экземпляра объекта.
Обычные объекты занимают 8 байт, а массивы занимают 12 байт (8 байт заголовка обычного объекта + 4 байта длины массива)
базовый тип
логическое значение, байт занимает 1 байт
char,short занимают 2 байта
int, float занимает 4 байта
long,double занимает 8 байт
Ссылочные типы: каждый ссылочный тип занимает 4 байта.
Заполнитель: рассчитывается кратно 8, кратность меньше 8 будет автоматически заполнена.
Объект User выше имеет два свойства, идентификатор типа int занимает 4 байта, имя ссылочного типа занимает 4 байта, плюс заголовок объекта 8 байтов, это ровно 16 байтов.
Суммировать
Есть еще много важных знаний о виртуальных машинах.Если вы знаете, как писать качественный код, оптимизировать производительность и устранять неполадки, это будет вишенкой на торте. компилятор проведет оптимизацию по результатам escape-анализа и скалярной подстановки. Заинтересованные друзья могут проверить информацию и узнать сами. Благодаря этому примеру размещения в стеке, когда мы будем писать код в будущем, мы будем писать объекты, которые не могут убежать в тело метода, что будет оптимизировано компилятором и улучшит производительность. А еще я знаю ответ на вопрос выше из интервью, то есть объекты в Java не обязательно размещаются в куче, а могут располагаться в стеке
использованная литература
- «Практическая виртуальная машина Java»
- "Глубокое понимание виртуальной машины Java"
- en.wikipedia.org/wiki/Анализ побега
Добро пожаловать в официальный аккаунт [Белые зубы каждый день], получайте свежие статьи, давайте общаться и добиваться успехов вместе!