JVM-серия фактического исключения переполнения боевой памяти

Java задняя часть JVM VisualVM

Фактическое исключение переполнения памяти

Привет всем, я считаю, что большинство Javaers часто сталкиваются с локальным кодом, работающим нормально при кодировании, но иногда в производственной среде необъяснимым образом сообщают о некоторых исключениях, связанных с памятью, исключения StackOverFlowError, OutOfMemoryError являются наиболее распространенными. Сегодня основано на предыдущей статьеПодробное объяснение структуры памяти Java серии JVMКаждая поясняемая область памяти фокусируется на ситуации переполнения памяти при анализе реального боя. Перед этим я еще хочу повторить некоторые другие вопросы об объектах, а именно:

Структура статьи

  1. процесс создания объекта
  2. Структура памяти объекта
  3. место доступа к объекту
  4. Фактическое исключение памяти

1. Процесс создания объекта

Что касается создания объектов, то первой реакцией является новое ключевое слово, поэтому в этой статье в основном объясняется процесс создания объектов с помощью нового ключевого слова.

Student stu =new Student("张三","18");

Возьмите приведенный выше код в качестве примера, виртуальная машина сначала проверит, был ли загружен класс Student. Если нет, сначала загрузите класс в область методов, а затем создайте объект экземпляра stu в соответствии с загруженным объектом класса. Обратите внимание, что Да, объем памяти, требуемый объектом stu, находится в StudentПосле загрузки классаможно полностью определить. После того, как выделение памяти завершено, виртуальная машина должна выделить выделенное пространство памяти.данные экземпляраЧасти инициализируются нулевыми значениями, поэтому при написании Java-кода мы создаем переменную без инициализации. Далее виртуальная машина имеетзаголовок объектаСделайте необходимые настройки, например, к какому классу принадлежит объект, как найти метаданные класса (объект класса), информацию о блокировке объекта, возраст генерации GC и т. д. После установки информации заголовка объекта вызовите конструктор класса.
На самом деле, если честно, процесс создания объектов в виртуальной машине намного сложнее, я просто объясняю здесь общий контекст, чтобы облегчить ваше понимание.

2. Расположение объектов в памяти

Данные экземпляра и заголовок объекта, упомянутые только что, могут быть незнакомы некоторым друзьям. В этом разделе подробно объясняется расположение памяти объекта. После того, как объект создан, его можно условно разделить на следующие части:

  • заголовок объекта
  • данные экземпляра
  • Выровнять отступы

заголовок объекта: заголовок объекта содержит некоторую необходимую информацию во время работы объекта, такую ​​как информация о создании сборщика мусора, информация о блокировке, хэш-код, указатель на метаинформацию о классе и т. д. Среди них информация о блокировке и указатель на объект класса более полезны для Javaer. , Указатели на информацию о блокировках будут расширены позже, когда будет возможность объяснить параллельное программирование JUC.Указатель на объект Class на самом деле очень хорошо понят. Например, в приведенном выше примере Student, когда мы получаем объект stu и вызываем Class stuClass=stu.getClass();, мы фактически получаем объект класса, хранящийся в области методов класса Student, к которому принадлежит объект stu. на основе этого указателя. Хотя это немного сложно сказать, я обдумывал это предложение несколько раз, и оно должно быть ясным. ^_^

данные экземпляра: Часть данных экземпляра — это действующая информация, которую фактически хранит объект, то есть содержимое различных типов полей, определенных в программном коде.

Выровнять отступы: спецификация виртуальной машины требует, чтобы размер объекта был целым числом, кратным 8 байтам. Заполнение выравнивания на самом деле предназначено для завершения размера объекта.

3. Доступ к позиционированию объектов

Когда дело доходит до доступа к объекту, давайте в качестве примера возьмем пример студентов выше.Когда мы получаем объект stu и напрямую вызываем stu.getName();, доступ к объекту фактически завершен. Но я хочу повторить здесь, что хотя stu обычно рассматривается как объект, на самом деле это неточно, Stu — это просто переменная, а переменная хранит указатель на объект (если вы работали на C или C++, друзья должны быть более осведомлены о концепции указателей), когда мы вызываем stu.getName(), виртуальная машина находит объекты в куче в соответствии с указателем, а затем получает имя данных экземпляра. Следует отметить, что когда мы вызываем stu .getClass() , виртуальная машина сначала находит объект в куче в соответствии с указателем stu, а затем переходит в область методов, чтобы снова получить объект класса в соответствии с указателем на метаинформацию класса, хранящуюся в заголовке объекта, и выполняет поиск по двум указателям. Конкретное объяснение следующее:

4. Фактическое исключение памяти

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

концепция

  • Утечка памяти: объект в коде должен был быть восстановлен виртуальной машиной, но не был восстановлен, поскольку имел ссылку GCRoot. О концепции GCRoot будет рассказано в следующей статье.
  • Переполнение памяти: виртуальная машина не может продолжать создавать новые объекты, так как в куче слишком много неперерабатываемых объектов, которые не были освобождены.

Прежде чем анализировать проблему, позвольте мне рассказать вам, как устранить проблему переполнения памяти.Когда память переполняется, виртуальная машина JVM завершает работу, так как же мы можем узнать все виды информации, когда JVM работает?DumpВ этом нам поможет механизм: добавив в ВМ параметр -XX:+HeapDumpOnOutOfMemoryError, виртуальная машина может сгенерировать файл дампа при возникновении исключения переполнения памяти, а затем с помощью внешнего инструмента (автор использует VisualVM) специально проанализировать причину исключение.

Следующие аспекты используются для демонстрации переполнения памяти и того, как найти его с помощью кода реального боя:

  • Исключение памяти кучи Java
  • Исключение памяти стека Java
  • Исключение памяти области метода

Исключение памяти кучи Java

/**
    VM Args:
    //这两个参数保证了堆中的可分配内存固定为20M
    -Xms20m
    -Xmx20m  
    //文件生成的位置,作则生成在桌面的一个目录
    -XX:+HeapDumpOnOutOfMemoryError //文件生成的位置,作则生成在桌面的一个目录
    //文件生成的位置,作则生成在桌面的一个目录
    -XX:HeapDumpPath=/Users/zdy/Desktop/dump/ 
 */
public class HeapOOM {
    //创建一个内部类用于创建对象使用
    static class OOMObject {
    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        //无限创建对象,在堆中
        while (true) {
            list.add(new OOMObject());
        }
    }
}

После запуска кода исключение выглядит следующим образом:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/zdy/Desktop/dump/java_pid1099.hprof ...

Вы можете видеть, что файл дампа создается в указанном каталоге. И всплывает OutOfMemoryError, и он сообщает вам, в какой области есть проблема: место в куче

Откройте инструмент VisualVM и импортируйте соответствующий файл heapDump (пожалуйста, обратитесь к соответствующей информации о том, как его использовать), соответствующие инструкции показаны на рисунке:

"类标签"
"метка класса"

Перейдите на вкладку «Количество экземпляров».
"实例数标签"
«Номер экземпляра этикетки»

Проанализировав файл DUMP, мы можем узнать, что класс OomObject This создает 810 326 экземпляров. Так может ли он переполниться? Далее эта категория встречается в коде. Верный вопрос. (Наш пример кода не исследован, цикл while слишком жестокий)

Исключение памяти стека Java

Честно говоря, вероятность возникновения исключения (StackOverFlowError) в стеке настолько мала, что равна вероятности зайти в магазин Apple за мобильным телефоном и после покупки узнать, что это система Android. Потому что автор ни разу не сталкивался с этим в продакшене, разве что сам писал примеры тестов кода. Давайте сначала поговорим об исключении.Как упоминалось ранее, процесс вызова метода — это процесс входа и выхода кадра метода из стека виртуальной машины, тогда есть две ситуации, которые могут вызвать StackOverFlowError, когда кадр метода (например, требующий 2M памяти )) При входе в стек виртуальной машины (например, памяти осталось 1М) будет сообщение StackOverFlow.Вот такое понятие,Глубина стека: относится к кадру метода, который не выталкивается из текущего стека виртуальной машины.. Емкость стека виртуальной машины управляется параметром -Xss.Ниже приведен фрагмент кода для искусственного уменьшения емкости стека, а затем инициирования исключения через рекурсивные вызовы.

/**
 * VM Args:
    //设置栈容量为160K,默认1M
   -Xss160k
 */
public class JavaVMStackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        //递归调用,触发异常
        stackLeak();
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

Результат выглядит следующим образом:
stack length:751
Exception in thread "main" java.lang.StackOverflowError

Видно, что рекурсивный вызов совершается 751 раз, и емкости стека не хватает.
Емкость стека по умолчанию может достигать глубины 1000-2000 при вызове обычного метода, поэтому можно допустить общую рекурсию. Если ваш код выдает StackOverflowError, сначала проверьте код, а не изменяйте параметры.

Кстати, когда многие люди занимаются многопоточной разработкой, когда создается много потоков, часто возникает OOM (OutOfMemoryError).В это время проблема может быть решена путем уменьшения максимальной емкости кучи или емкости стека в соответствии с конкретная ситуация. Почему так? См. формулу ниже:

Количество потоков * (максимальная емкость стека) + максимальное значение кучи + остальная память (игнорируется или вообще не изменяется) = максимальная память машины

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

Исключение памяти области метода

Когда я писал это, автор изначально хотел написать пример бесконечного создания динамических прокси-объектов, чтобы продемонстрировать переполнение области методов и не говорить о переходе изменений области памяти между JDK7 и JDK8, но, подумав, я все же поставил этот кусок от начала до конца. в предыдущей статьеПодробное объяснение структуры памяти Java серии JVMГоворя об упомянутом методе зоны, в соответствии с JDK7 метод зоны среды включает (постоянный пул времени выполнения), на самом деле сказать, что это не точно. Поскольку с самого начала JDK7 команда HotSpot начала думать о «постоянной генерации», мы должны сначала очистить область концепции и метода «постоянная генерация» (пространство PermGen) — это две концепции, район методов — это спецификация виртуальной машины JVM, любая виртуальная машина реализация (J9 и т. д.) меньше этого интервала, и «постоянная генерация» — это всего лишь один метод реализации HotSpot региона. Чтобы знание указывало четко, я был только в виде списка:

  • До JDK7 (включая JDK7) существовало «пространство PermGen», которое использовалось для реализации области методов. Однако в JDK7 многие вещи были постепенно выведены из постоянного поколения в реализации, например: ссылки на символы (Symbols) перенесены в нативную кучу, пулы констант времени выполнения (интернированные строки) перенесены в кучу java; класс статические переменные (класс statics) передаются в кучу java.
    Вот почему я сказал, что в предыдущей статье было неправильно говорить, что область методов содержит пул констант времени выполнения, потому что он был перемещен в кучу java;
  • До JDK7 (включая 7) размер постоянного поколения можно было контролировать с помощью -XX:PermSize -XX:MaxPermSize.
  • JDK8 официально удалил «постоянное поколение» и заменил его на Metaspace как реализацию области методов в спецификации виртуальной машины JVM.
  • Самая большая разница между метапространством и постоянным поколением заключается в том, что метапространство находится не в виртуальной машине, а использует локальную память. Поэтому по умолчанию размер метапространства ограничен только локальной памятью, но размер по-прежнему можно контролировать параметрами: -XX:MetaspaceSize и -XX:MaxMetaspaceSize.

Автор по-прежнему использует фрагмент кода для непрерывного создания объектов Class.В JDK8 можно увидеть переполнение памяти metaSpace:

/**
  作者准备在JDK8下测试方法区,所以设置了Metaspace的大小为固定的8M
 -XX:MetaspaceSize=8m
 -XX:MaxMetaspaceSize=8m
 */
public class JavaMethodAreaOOM {

    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invokeSuper(obj, args);
                }
            });
            //无限创建动态代理,生成Class对象
            enhancer.create();
        }
    }

    static class OOMObject {

    }
}

В среде JDK8 будет сообщено об исключении:
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
Это связано с тем, что генерируется динамический прокси-класс при вызове прокси-сервера CGLIB CGLIB, то есть объект класса отправляется в MetaSpace, поэтому возникает исключение, когда это происходит.
Напоминание: хотя мы каждый день называем это «дампом кучи», технология дампа эффективна не только для области «кучи», но и для OOM, то есть независимо от того, в какой области, любой, кто может сообщить об ошибке OOM, может использовать Техника дампа создает файлы дампа для анализа.

В приложениях, которые часто динамически генерируют большое количество классов, особое внимание следует уделить статусу утилизации классов.Помимо технологии CGLib в примере также присутствует большое количество JSP, отражения, OSGI и т. д. этот сценарий. Требуется особое внимание, когда происходит такое исключение, вы должны знать, где проблема, а затем корректировать параметры или оптимизировать на уровне кода.

Добавить — исключение прямой памяти

Исключения прямой памяти очень редки, и механизм очень особенный, потому что прямая память напрямую не выделяет память операционной системе, а рассчитанной памяти недостаточно и выдает исключение вручную, поэтому, когда вы обнаружите, что ваш файл дампа мал , и явного исключения нет, просто скажите вам OOM, вы можете учитывать, используется ли NIO прямо или косвенно в вашем коде, что приводит к прямому переполнению памяти.

Что ж, «серия JVM о фактическом исключении переполнения боевой памяти» здесь, чтобы представить вам, хорошего дня Добро пожаловать, чтобы оставить сообщение, чтобы указать на ошибку.

Прошлый вход:

  1. Подробное объяснение структуры памяти Java серии JVM