1. Предисловие
Java-это объектно-ориентированный объектно-ориентированный язык платформы. Программы Java работают на виртуальных машинах Java (JVM) и управляют памятью JVMS. Это самая большая разница от C ++. Хотя память управляется JVM, мы также должны понять, как JVM управляет памятью. Существует не только один JVM, и в настоящее время могут существовать десятки виртуальных машин, но и конструкция виртуальной машины, которая соответствует спецификации, должна следовать «спецификации виртуальной машины Java». Эта статья основана на описании виртуальной машины горячей точки и будет упомянута, если есть различия с другими виртуальными машинами. В этой статье в основном описывается, как память распространяется в JVM, как объекты программы Java хранятся и доступны, и возможные исключения в различных областях памяти.
2. Распределение памяти (область) в JVM
При выполнении Java -программ JVM делит память на множество различных областей данных для управления. Эти области имеют разные функции, время создания и разрушения. Некоторые области выделяются, когда процесс JVM запускается, в то время как другие связаны с жизненным циклом пользовательского потока (поток самой программы). Согласно спецификации JVM, области памяти, управляемые JVM, разделены на следующие области данных времени выполнения:
1. Виртуальный стек машины
Эта область памяти является приватной по потоке и создается по мере того, как поток запускается и разрушается, когда она уничтожена. Модель памяти для выполнения методов Java, описанных стеком виртуальных машин: каждый метод создаст кадр стека (кадр стека) в начале выполнения, который используется для хранения локальных таблиц переменных, стеков операндов, динамических ссылок, выходов методов и т. Д.
Как следует из названия, локальная таблица переменных представляет собой область памяти, в которой хранятся локальные переменные: она хранит основные типы данных (8 основных типов данных Java), типов ссылок и возвращаемых адресов, которые можно найти в течение периода компилятора; Длинные и двойные типы, которые занимают 64 бита, будут занимать 2 локальных переменных пространства, а другие типы данных занимают только 1; Поскольку размер типа определяется и количество переменных может быть известно в течение периода компиляции, локальная таблица переменной имеет известный размер при создании. Эта часть пространства памяти может быть выделена в течение периода компиляции, и нет необходимости изменять локальный размер таблицы переменных во время выполнения метода.
В спецификации виртуальной машины для этой области памяти указаны два исключения:
1. Если глубина стека, запрашиваемая потоком, больше, чем допустимая глубина (?), Будет брошено исключение StackOverflowError ;
2. Если виртуальная машина может раскрываться динамически, когда расширение не может применяться для достаточной памяти, будет выброшено исключение OutOfMemory ;
2. локальный стек методов
Локальный стек методов также является потоковым, а его функция почти такая же, как стек виртуальных машин: стек виртуальных машин предоставляет услуги стека для и выхода для выполнения метода Java, в то время как локальный стек методов предоставляет услуги для виртуальной машины для выполнения собственных методов.
В спецификации виртуальной машины не существует обязательного регулирования в методе реализации локального стека методов, и он может быть свободно реализован конкретной виртуальной машиной; Виртуальная машина горячей точки напрямую объединяет стек виртуальных машин и стек локального метода в один; Для других виртуальных машин для реализации этого метода читатели могут запросить соответствующую информацию, если они заинтересованы;
Как и стек виртуальных машин, локальный стек методов также будет бросать исключения StackOverflowError和OutOfMemory .
3. Программный калькулятор
Калькулятор программы также является частной областью памяти потоков. Его можно рассматривать как индикатор номера строки (указывая на инструкцию) для потоков для выполнения байт -кода. Когда Java выполняется, она получает следующую инструкцию, которая будет выполнена путем изменения значения счетчика. Заказы о выполнении ветвей, петель, прыжки, обработка исключений, восстановление потока и т. Д. Все полагаются на этот счетчик для завершения. Многопользовательское обозначение виртуальной машины достигается путем переключения по очереди и распределения времени выполнения процессора. Процессор (ядро для многоядерного процессора) может выполнять только одну команду за раз. Следовательно, после того, как поток выполняет переключение, его необходимо восстановить в правильную позицию выполнения. Каждый поток имеет независимый калькулятор программы.
При выполнении метода Java программный калькулятор записывает (указывает) адрес инструкции по байткоду, который выполняет текущий поток. Если собственный метод выполняется, значение этого калькулятора не определен. Это связано с тем, что модель потока виртуальной машины Hotspot является собственной моделью потока, то есть каждый поток Java напрямую отображает поток ОС (операционная система). При выполнении собственного метода он напрямую выполняется ОС. Значение этого счетчика виртуальной машины бесполезно; Поскольку этот калькулятор представляет собой область памяти с очень небольшим пространством, частным и не требует расширения. Это единственная область в спецификации виртуальной машины, в которой не указано какое -либо исключение OutOfMemoryError .
4. Хиповая память (куча)
Java Heap - это область памяти, разделяемая потоками. Можно сказать, что это самая большая область памяти, управляемая виртуальной машиной, и создается при запуске виртуальной машины. В памяти Java в основном хранится экземпляры объектов, и здесь хранятся почти все экземпляры объекта (включая массивы). Следовательно, это также основная область памяти сбора мусора (GC). Содержание о GC не будет описано здесь;
Согласно спецификации виртуальной машины, память о куче Java может быть в прерывистой физической памяти. До тех пор, пока он логически непрерывно и нет ограничения на расширение пространства, это может быть либо фиксированный размер, либо расширенное дерево. Если в памяти кучи не хватает места для завершения распределения экземпляра и не может быть расширена, будет выброшено исключение OutOfMemoryError .
5. Метод зона
Область метода представляет собой область памяти, разделяемая потоками, точно так же, как память кучи, она хранит информацию типа, константы, статические переменные, код, составленный в течение периода мгновенного компиляции и других данных. Спецификация виртуальной машины не имеет слишком большого количества ограничений на реализацию области метода, и, как и память кучи, она не требует непрерывного пространства физической памяти, размер может быть фиксированным или масштабируемым, и его также можно выбрать, чтобы не реализовать сбор мусора; Когда область метода не может соответствовать требованиям распределения памяти, исключение OutOfMemoryError будет брошено.
6. Прямая память
Прямая память не является частью управляемой памяти виртуальной машины, но эта часть памяти все еще может использоваться часто; Когда в программах Java используются собственные методы (такие как Nio, Nio, здесь не указано в описаниях), память может быть выделена непосредственно за пределами HEAP, но общее пространство памяти ограничено, и также будет недостаточное количество памяти, а также будет выбрано исключение OutOfMemoryError .
2. Доступ к хранению объектов экземпляра
Первая точка выше имеет общее описание памяти в каждой области виртуальной машины. Для каждой области возникают проблемы с тем, как данные создаются, изложены и доступны. Давайте используем наиболее часто используемую память кучи в качестве примера для разговора об этих трех аспектах, основанных на точке горячей точки.
1. Создание объекта экземпляра
Когда виртуальная машина выполняет новую инструкцию, сначала она сначала определяет ссылку на символ класса объекта создания из постоянного пула и судьи, независимо от того, был ли класс загружен и инициализирован. Если он не загружен, процесс инициализации загрузки класса будет выполнен (описание не будет сделано здесь о загрузке класса). Если этот класс не может быть найден, будет брошено общее исключение ClassNotFoundException ;
После проверки загрузки класса физическая память (память кучи) фактически выделяется на объект. Пространство памяти, требуемое объектом, определяется соответствующим классом. После загрузки класса пространство памяти, требуемое объектом этого класса, исправлено; Распределение пространства памяти для объекта эквивалентно разделению кусочки от кучи и распределению его на этот объект;
В соответствии с тем, непрерывно ли пространство памяти (выделено и не раскрывается, делится на две полные части), оно разделено на два способа распределения памяти:
1. Непрерывная память: указатель используется в качестве разделительной точки между выделенной и нераспределенной памятью. Распределение памяти объекта требует, чтобы указатель перемещал размер пространства в сегмент нераспределенного памяти; Этот метод называется «столкновением указателя».
2. Прерывистая память: виртуальная машина должна поддерживать (записывать) список, который записывает эти блоки памяти в куче, которые не выделяются. При распределении памяти объекта выберите область памяти подходящего размера, чтобы распределить ее на объект и обновить этот список; Этот метод называется «Свободный список».
Распределение памяти объектов также столкнется с проблемами параллелизма. Виртуальная машина использует два решения для решения этой проблемы безопасности потока: во -первых, используйте CAS (сравните и установите)+ для идентификации и повторения, чтобы обеспечить атомичность операции распределения; Во-вторых, распределение памяти делится на разные пространства в соответствии с потоками, то есть каждая потока предварительно выделяла кусок резьбовой памяти в куче, называемый локальным буфером с расплавленным потоком (TLAB); Когда этот поток хочет выделить память, она прямо выделяется из TLAB. Только когда TLAB потока выделяется после повторного распределения, может ли синхронная операция быть выделена из кучи. Это решение эффективно снижает параллелизм памяти куча распределения объектов между потоками; Используется ли виртуальная машина TLAB через параметр JVM -xx: +/- usetlab.
После завершения распределения памяти, в дополнение к информации заголовка объекта, виртуальная машина инициализирует выделенное пространство памяти до нулевого значения, чтобы гарантировать, что поля экземпляра объекта можно напрямую использоваться с нулевым значением, соответствующим типу данных, без присвоения значений; Затем выполните метод инициализации, чтобы завершить инициализацию в соответствии с кодом до завершения создания объекта экземпляра;
2. макет объектов в памяти
В виртуальной машине горячей точки объекты делятся на три части в памяти: заголовок объекта, данные экземпляра, а также выравнивание и заполнение:
Заголовок объекта разделен на две части: часть ИТ хранит данные времени выполнения объекта, включая хэш -код, возраст генерации мусора, состояние блокировки объекта, блокировка хранения потока, идентификатор склонного потока, смещенную временную метку и т. Д.; В 32-битных и 64-битных виртуальных машинах эта часть данных занимает 32-битную и 64-битную соответственно; Поскольку существует много данных времени выполнения, 32-разрядные или 64-битные недостаточно, чтобы полностью сохранить все данные, поэтому эта часть предназначена для хранения данных времени выполнения в нефиксированном формате, но использует различные биты для хранения данных в соответствии с состоянием объекта; Другая часть хранит указатель типа объекта, указывающий на класс этого объекта, но это не обязательно, и метаданные класса объекта не обязательно должны быть определены с использованием этой части хранилища (оно будет обсуждаться ниже);
Данные экземпляра - это содержание различных типов данных, определенных объектом, и данные, определенные этими программами, не хранятся в определенном порядке. Они определяются в порядке политик и определений распределения виртуальных машин: Long/Double, Int, Short/Char, Byte/Boolean, OOP (обычный объект Ponint) , видно, что политики выделяются в соответствии с количеством заполнителей типа, и одни и те же типы будут выделять память вместе; и при удовлетворении этих условий порядок переменных родительского класса предшествует подклассу;
Часть заполнения объекта не обязательно существует. Это играет только роль в выравнивании заполнителей. В управлении памятью виртуальной машины управление виртуальной машиной управляется в единицах 8 байтов. Следовательно, когда память распределяется, размер объекта не радует 8, а заполнение выравнивания завершено;
3. Доступ к объектам В спецификации виртуальной машины только предусмотрено, что эталонный тип является ссылкой, указывающим на объект, и не указывает, как эта ссылка определяет и обращается к экземплярам в куче; В настоящее время в основных виртуальных машинах есть два основных способа реализации доступа к объектам:
1. Способ обработки: область разделена на память кучи в виде пула ручки. Справочная переменная хранит адрес грандиозного объекта объекта, а ручка хранит конкретную информацию о адресах образца объекта и типа объекта. Следовательно, заголовок объекта не может содержать тип объекта:
2. Прямой доступ к указателю: тип ссылки непосредственно хранит информацию о адресах объекта экземпляра в куче, но это требует, чтобы макет объекта экземпляра должен был содержать тип объекта:
Эти два метода доступа имеют свои собственные преимущества: когда адрес объекта изменяется (сортировка памяти, сбор мусора), объект доступа к ручке, эталонная переменная не должна изменяться, но изменение значения адреса объекта в ручке изменяется; При использовании метода прямого доступа указателя все ссылки этого объекта необходимо изменить; Но метод указателя может уменьшить одну операцию адресации, и в случае большого количества доступных объектов преимущества этого метода более очевидны; Виртуальная машина горячей точки использует этот метод прямого доступа.
3. Исключение памяти времени выполнения
Существует два основных исключения, которые могут произойти при запуске в программе Java: OutofmemoryError и StackoverFlowerRor; Что будет в этой области памяти? Как упоминалось кратко ранее, за исключением счетчика программы, будут происходить другие области памяти; Этот раздел в основном демонстрирует исключения в каждой области памяти через код экземпляра, и многие часто используемые параметры запуска виртуальной машины будут использоваться для лучшего объяснения ситуации. (Как запустить программу с параметрами, здесь не описано)
1. переполнение памяти на джава
Переполнение памяти кучи происходит, когда объекты создаются после того, как емкость кучи достигает максимальной емкости кучи. В программе объекты создаются непрерывно, и эти объекты гарантированно не собираются мусор:
/** * Параметры виртуальной машины: * -xms20m минимальная емкость кучи * -xmx20m максимальная емкость кучи * @author hwz * */public class headoutofmemoryerr {public static void main (string [] argous) {// Список объекта. ArrayList <HeadoutofmemoryError> (); while (true) {// непрерывно создавать объекты и добавить их в контейнеры Listtoholdobj.add (новый HeadoutofmemoryError ()); }}} Вы можете добавить параметры виртуальной машины :-XX:HeapDumpOnOutOfMemoryError . При отправке исключения OOM позвольте виртуальной машине сбросить файл снимка текущей кучи. В будущем вы можете использовать эту проблему сегментации слова «Сегментация слова». Это не будет описано подробно. Я напишу блог, чтобы подробно описать инструмент Mat для анализа проблем с памятью.
2. Стек виртуальных машин и переполнение стека локального метода
В виртуальной машине горячей точки эти два стека метода не реализованы вместе. Согласно спецификации виртуальной машины, эти два исключения будут происходить в этих двух областях памяти:
1. Если поток запрашивает глубину стека, превышающую максимальную глубину, разрешенную виртуальной машиной, добавьте исключение StackoverFlowerRor;
2. Если виртуальная машина не может применить к большому пространству памяти при расширении пространства стека, будет брошено исключение OutofmemoryError;
На самом деле существует перекрытие между этими двумя ситуациями: когда пространство стека не может быть выделено, невозможно ли отличить, слишком ли маленькая память или глубина используемого стека слишком велика.
Используйте два способа проверки кода
1. Используйте параметр -xss, чтобы уменьшить размер стека, вызов метода бесконечно рекурсивно и бесконечно увеличить глубину стека:
/** * Параметры виртуальной машины: <br> * -xss128k емкость стека * @author hwz * */public class stackoverflowerror {private int stackdeep = 1; / *** Бесконечная рекурсия, бесконечно увеличивает глубину стека вызовов*/ public void recursiveinvoke () {StackDeep ++; recursiveinvoke (); } public static void main (string [] args) {stackoverFlowerRor soe = new StackOverFlowerRor (); try {soe.recursiveinvoke (); } catch (throwable e) {System.out.println ("stack deep =" + soe.stackdeep); бросить E; }}} Большое количество локальных переменных определяется в методе, длина локальной таблицы переменной в стеке методов также называется бесконечно рекурсивно:
/** * @author hwz * */public class Stackoomeerror {private int Stackdeep = 1; / *** Определить большое количество локальных переменных, увеличить локальную таблицу переменных в стеке* бесконечная рекурсия, бесконечно увеличивает глубину стека вызовов*/ public void recursiveinvoke () {Double I; Двойной i2; //......... recursiveinvoke (); } public static void main (string [] args) {StackOomeerror soe = new StackOomeError (); try {soe.recursiveinvoke (); } catch (throwable e) {System.out.println ("stack deep =" + soe.stackdeep); бросить E; }}}Приведенный выше тест кода показывает, что независимо от того, является ли стек кадров слишком велик или емкость виртуальной машины слишком мала, когда память не может быть выделена, все StackoverFlowerRor выбрасывается;
3. Метод зона и постоянный переполнение бассейна выполнения
Здесь мы сначала опишем метод строки стажера: если пул строки постоянной константы уже содержит строку, равную этому объекту строки, он вернет объект строки, представляющий эту строку. В противном случае добавьте этот объект строки в постоянный пул и верните ссылку на этот объект строки; С помощью этого метода он будет постоянно добавлять строковый объект в постоянный пул, что приведет к переполнению:
/** * Параметры виртуальной машины: <br> * -xx: permsize = 10m постоянный размер области * -xx: maxpermsize = 10m постоянная область максимальная емкость * @author hwz * */public class runtimeconstancepooloom ArrayList <string> (); // Используйте метод string.Intern для добавления объекта постоянного пула для (int i = 1; true; i ++) {list.add (string.valueof (i) .intern ()); }}}Тем не менее, этот тестовый код не переполняется во время постоянного пула времени выполнения в JDK1.7, но это произойдет в JDK1.6. По этой причине напишите еще один тестовый код, чтобы проверить эту проблему:
/** * String.Intern Метод тестируется в различных JDKS * @Author HWZ * */Public Class StringInternTest {public Static void main (string [] args) {string str1 = new StringBuilder ("test"). Append ("01"). ToString (); System.out.println (str1.intern () == str1); String str2 = new StringBuilder ("test"). Append ("02"). ToString (); System.out.println (str2.intern () == str2); }} Результаты работы в соответствии с JDK1.6: ложь, ложь;
Результатом работы в рамках JDK1.7 является: true, true;
Оказывается, что в JDK1.6 метод inger () копирует первый экземпляр String String в постоянную генерацию, что, в свою очередь, является ссылкой на экземпляр в постоянной генерации, а экземпляры строки, созданные StringBuilder, находятся в куче, поэтому они не равны;
В JDK1.7 метод inger () не копирует экземпляр, а только записывает ссылку на первый экземпляр, который появляется в постоянном пуле. Следовательно, ссылка, возвращаемая negh, такая же, как и экземпляр, созданный StringBuilder, поэтому он возвращает true;
Следовательно, тестовый код для постоянного переполнения пула не будет иметь постоянного исключения переполнения пула, но может иметь недостаточное исключение переполнения памяти кучи после непрерывного запуска;
Затем вам необходимо проверить переполнение области метода, просто продолжать добавлять вещи в область метода, например, имена классов, модификаторы доступа, постоянные пулы и т. Д. Мы можем позволить программе загружать большое количество классов, чтобы непрерывно заполнять область метода, что приводит к переполнению. Мы используем Cglib для непосредственного манипулирования байт -кодом для генерации большого количества динамических классов:
/** * Метод области переполнения тестового класса Enhancer.setSuperClass (maoomclass.class); Enhancer.SetUseCache (false); Enhancer.SetCallback (New MethodInterceptor () {@Override Public Object Intercept (объект obj, метод метода, объект [] args, methodproxy proxy) бросает Throwable {return proxy.invokesuper (obj, args);}}); Enhancer.create (); }} статический класс maoomclass {}} Благодаря наблюдению VisualVM мы видим, что количество классов, загруженных JVM, увеличивается по прямой линии с использованием Pergen:
4. Прямой переполнение памяти
Размер прямой памяти может быть установлен через параметры виртуальной машины : -xx: maxdirectmemorysize . Чтобы сделать прямой переполнение памяти, вам нужно только постоянно применяться к прямой памяти. Ниже приведено то же самое, что и прямой тест кэша памяти в Java Nio:
/** * Параметры виртуальной машины: <br> * -xx: maxDirectMemorySize = 30M Прямой размер памяти * @author hwz * */public class directmemoryoom {public static void main (string [] args) {list <uffer> buffers = new ArrayList <Buffer> (); int i = 0; while (true) {// печатать текущую систему.out.println (++ i); // Прямое потребление памяти путем непрерывного применения для прямого потребления памяти буфера в буфере кэша. // бухгалтерский учет 1 м каждый раз}}} В цикле каждый раз, когда применяется прямая память 1M, максимальная прямая память устанавливается на 30 м, а исключение бросается, когда программа работает 31 раза: java.lang.OutOfMemoryError: Direct buffer memory
4. Резюме
Выше всего содержание этой статьи. В этой статье в основном описывается структура макета памяти, хранения объектов и исключений памяти, которые могут возникнуть в различных областях памяти в JVM; Основной справочник «подробное понимание виртуальной машины Java (второе издание)». Если есть какая -то неверная, пожалуйста, укажите это в комментариях; Спасибо за поддержку Wulin.com.