10 распространенных OOM-анализов — научим писать баги

JVM
10 распространенных OOM-анализов — научим писать баги

Нравится + Избранное, чтобы узнать серию, статьи включены в GitHubJavaKeeper, N-line интернет-разработка, спектр основных навыков оружия, делайте заметки самостоятельно

В «Спецификации виртуальной машины Java», помимо счетчика программ, несколько других областей времени выполнения в памяти виртуальной машины могут иметь исключение OutOfMemoryError.

Эта статья в основном включает следующее введение и примеры OOM:

  • java.lang.StackOverflowError
  • java.lang.OutOfMemoryError: Java heap space
  • java.lang.OutOfMemoryError: GC overhead limit exceeded
  • java.lang.OutOfMemoryError-->Metaspace
  • java.lang.OutOfMemoryError: Direct buffer memory
  • java.lang.OutOfMemoryError: unable to create new native thread
  • java.lang.OutOfMemoryError: Метапространство
  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit
  • java.lang.OutOfMemoryError: Out of swap space
  • java.lang.OutOfMemoryError: убить процесс или пожертвовать дочерним элементом

Мы часто говорим, что исключение OOM на самом деле является ошибкой.

1. StackOverflowError

1.1 Написать ошибку

public class StackOverflowErrorDemo {

    public static void main(String[] args) {
        javaKeeper();
    }

    private static void javaKeeper() {
        javaKeeper();
    }
}

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

Exception in thread "main" java.lang.StackOverflowError
	at oom.StackOverflowErrorDemo.javaKeeper(StackOverflowErrorDemo.java:15)

1.2 Анализ причин

  • Вызывая ситуацию бесконечным рекурсивным циклом (наиболее распространенная причина), мы всегда должны обращать внимание на то, циркулирует ли код, вызывающий метод, не может выйти
  • Выполняется большое количество методов, что приводит к исчерпанию пространства стека потока.
  • В методе объявлено большое количество локальных переменных
  • Нативный код имеет логику, размещенную в стеке, и требуемая память не мала, например, java.net.SocketInputStream.read0 потребует выделения кэша 64 КБ в стеке (64-разрядный Linux).

1.3 Решения

  • Исправьте код исключения, который запускает бесконечные рекурсивные вызовы, найдите повторяющиеся строки кода через стек исключений, выброшенный программой, следуйте карте и исправьте ошибку бесконечной рекурсии.
  • Проверьте, существует ли круговая зависимость между классами (это исключение также генерируется, когда метод ToString называется, когда два объекта относятся друг к другу)
  • По параметрам запуска JVM-XssУвеличьте объем памяти стека потоков. В некоторых сценариях обычного использования необходимо выполнить большое количество методов или включить большое количество локальных переменных. В этом случае можно соответствующим образом увеличить ограничение на пространство стека потоков.

2. Пространство кучи Java

Куча Java используется для хранения экземпляров объектов. Нам нужно только постоянно создавать объекты и обеспечивать доступный путь между корнями GC и объектами, чтобы избежать очистки этих объектов GC. По мере увеличения количества объектов общая емкость будет достигать максимальное ограничение емкости кучи, тогда произойдет исключение переполнения памяти.

Исключение OOM памяти кучи Java является наиболее распространенным исключением переполнения памяти в практических приложениях.

2.1 Написать ошибку

/**
 * JVM参数:-Xmx12m
 */
public class JavaHeapSpaceDemo {

    static final int SIZE = 2 * 1024 * 1024;

    public static void main(String[] a) {
        int[] i = new int[SIZE];
    }
}

Код пытается выделить массив int емкостью 2М, если указан параметр запуска-Xmx12m, выделения памяти недостаточно, это похоже на запихивание объекта с номером XXXL в пространство кучи Java с номером S.

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:13)

2.2 Анализ причин

  • Запрос на создание очень большого объекта, обычно большого массива
  • Превышение ожидаемого объема трафика/данных обычно вызвано всплеском трафика исходящих системных запросов, который часто наблюдается в различных акциях/секкиллах.Вы можете проверить наличие резких пиков на основе индикаторов бизнес-трафика.
  • Чрезмерное использование финализаторов, объект не сразу GCed
  • Утечка памяти (Memory Leak), большое количество ссылок на объекты не освобождается, и JVM не может их автоматически перерабатывать.Обычно, когда такие ресурсы, как файлы, используются, а не перерабатываются.

2.3 Решения

В большинстве случаев обычно необходимо только пройти-XmxПараметр можно настроить для увеличения объема памяти кучи JVM. Если проблема все еще не решена, вы можете обратиться к следующим ситуациям для дальнейшей обработки:

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

img

Интервьюер: Расскажи мне о утечках памяти и переполнением памяти

Добавьте очко знаний, и трое последовательных в конце концов станут великими богами~~

Утечки памяти и переполнение памяти

Переполнение памяти (out of memory) означает, что когда программа обращается за памятью, ей не хватает места для использования памяти, и происходит нехватка памяти; например, запрашивается целое число, но число, которое может быть сохранено Для него хранится долго, то есть Out of memory.

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

Утечка памяти в конечном итоге приведет к нехватке памяти!

3. Превышен предел накладных расходов GC

JVM имеет встроенный механизм сборки мусора GC, поэтому нам, как Javaers, не нужно вручную писать код для выделения и освобождения памяти, но когда процесс Java тратит более 98% времени на выполнение GC, только менее 2% памяти восстанавливается, а если действие повторить 5 раз подряд, то выкинетjava.lang.OutOfMemoryError:GC overhead limit exceededОшибка(Распространенное название: сборка мусора). Проще говоря, приложение фактически исчерпало всю доступную память, и сборщик мусора не может ее восстановить.

если не броситьGC overhead limit exceededНеправильно, этот небольшой кусок памяти, очищенный сборщиком мусора, скоро снова будет заполнен, заставляя сборщик мусора выполняться снова, этот порочный круг, использование ЦП составляет 100%, и сборщик мусора не имеет никакого эффекта.

3.1 Написать ошибку

В случае с этой ошибкой, по сути, мы пишем бесконечный цикл, а добавление данных в List или Map будет держать Full GC до тех пор, пока не выдержит.Здесь мы используем каштан, который не просто найти. Добавляем на карту 1000 элементов.

/**
 * JVM 参数: -Xmx14m -XX:+PrintGCDetails
 */
public class KeylessEntry {

    static class Key {
        Integer id;

        Key(Integer id) {
            this.id = id;
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }
    }

    public static void main(String[] args) {
        Map m = new HashMap();
        while (true){
            for (int i = 0; i < 1000; i++){
                if (!m.containsKey(new Key(i))){
                    m.put(new Key(i), "Number:" + i);
                }
            }
            System.out.println("m.size()=" + m.size());
        }
    }
}
...
m.size()=54000
m.size()=55000
m.size()=56000
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

Как видно из вывода, наш лимит в 1000 единиц данных не сработал, емкость карты намного превысила 1000, и, наконец, возникла нужная нам ошибка, потому что класс Key только переписался.hashCode()метод без переопределенияequals()метод, мы используемcontainsKey()На самом деле проблема в методе, поэтому ключ будет добавляться в HashMap до тех пор, пока сборщик мусора не сможет его очистить.

🧑🏻‍💻 Интервьюер снова здесь: расскажите о принципе HashMap и почему нужно реализовывать equals и hashcode одновременно

Окончательная ошибка выполнения этой программы также будет связана с конфигурацией JVM.Если установленная память кучи особенно мала, об этом будет сообщено напрямую.Java heap space. Эта ошибка немного разочаровывает, поэтому иногда, учитывая ограничения ресурсов, невозможно точно предсказать, по какой конкретной причине программа умрет.

3.2 Решения

  • Добавьте аргументы JVM-XX:-UseGCOverheadLimitЭто не рекомендуется, на самом деле это не решает проблему, а просто откладывает исключение.
  • Проверьте, много ли в проекте бесконечных циклов или кода, использующего большой объем памяти, оптимизируйте код
  • Анализ дампа памяти, проверьте есть ли утечка памяти, если нет, увеличьте память

Четыре, Прямая буферная память

Когда мы используем NIO, нам часто нужно использовать ByteBuffer для чтения или записи данных. Это метод ввода / вывода, на основе канала (канала) и буфера (буфера). Он может напрямую выделить память вне кучи, используя библиотеку собственной функции. Затем работайте через объект DirectbyTebuffer, хранящийся в куче Java в качестве ссылки на эту память. Это позволяет избежать копирования данных взад и вперед между кучей java и родным в некоторых сценариях, поэтому производительность будет улучшена.

Java позволяет приложениям получать прямой доступ к памяти вне кучи через Direct ByteBuffer, и многие высокопроизводительные программы используют Direct ByteBuffer в сочетании с Memory Mapped File для достижения высокоскоростного ввода-вывода.

4.1 Написать ошибку

  • ByteBuffer.allocate(capability)Это для выделения памяти кучи JVM, которая принадлежит юрисдикции GC и требует копирования памяти, поэтому скорость относительно низкая;

  • ByteBuffer.allocateDirect(capability)Он предназначен для выделения локальной памяти ОС, которая не относится к юрисдикции GC, относительно быстро, поскольку не требует копирования памяти;

Если локальная память выделяется постоянно, а память кучи используется редко, то JVM не нужно выполнять GC, и объект DirectByteBuffer не будет переработан.В это время, хотя памяти кучи достаточно, локальная память может не будет достаточно, и произойдет OOM.Локальная переполнение прямой памяти.

/**
 *  VM Options:-Xms10m,-Xmx10m,-XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 */
public class DirectBufferMemoryDemo {

    public static void main(String[] args) {
        System.out.println("maxDirectMemory is:"+sun.misc.VM.maxDirectMemory() / 1024 / 1024 + "MB");

        //ByteBuffer buffer = ByteBuffer.allocate(6*1024*1024);
        ByteBuffer buffer = ByteBuffer.allocateDirect(6*1024*1024);

    }
}

Максимальная прямая память, по умолчанию 1/4 памяти компьютера, поэтому ставим маленькую точку, а затем используем прямую память, чтобы превысить это значение, и появится OOM.

maxDirectMemory is:5MB
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

4.2 Решения

  1. Java может пройти толькоByteBuffer.allocateDirectМетод использует Direct ByteBuffer, поэтому вы можете перехватить этот метод для устранения неполадок с помощью онлайн-инструментов диагностики, таких как Arthas.
  2. Проверьте, используется ли NIO прямо или косвенно, например netty, jetty и т. д.
  3. через параметры запуска-XX:MaxDirectMemorySizeОтрегулируйте верхний предел Direct ByteBuffer
  4. Проверьте, имеют ли аргументы JVM-XX:+DisableExplicitGCвариант, если он есть, удалите его, потому что этот параметр сделаетSystem.gc()недействителен
  5. Проверьте код использования памяти вне кучи на наличие утечек памяти или вызовите его через отражениеsun.misc.Cleanerизclean()Метод активного освобождения пространства памяти, удерживаемого Direct ByteBuffer.
  6. Объема памяти действительно недостаточно, обновите конфигурацию

Пять, невозможно создать новый собственный поток

Каждый поток Java должен занимать определенный объем памяти.Когда JVM запрашивает базовую операционную систему для создания нового собственного потока, если выделено недостаточно ресурсов, будет сообщено об ошибке.

5.1 Написать ошибку

public static void main(String[] args) {
  while(true){
    new Thread(() -> {
      try {
        Thread.sleep(Integer.MAX_VALUE);
      } catch(InterruptedException e) { }
    }).start();
  }
}
Error occurred during initialization of VM
java.lang.OutOfMemoryError: unable to create new native thread

5.2 Анализ причин

Когда JVM не может запросить у ОС создание собственного потока, она выдаетUnableto createnewnativethread, общие причины включают следующие категории:

  • Количество потоков превышает максимальное количество потоков операционной системы (в зависимости от платформы)
  • Количество потоков превышает значение kernel.pid_max (только перезапуск)
  • Недостаточно собственной памяти; общий процесс возникновения этой проблемы в основном включает следующие шаги:
    1. Приложение внутри JVM запрашивает создание нового потока Java;
    2. Собственный метод JVM проксирует запрос и запрашивает у операционной системы создание собственного потока;
    3. Операционная система пытается создать новый собственный поток и выделить для него память;
    4. Если виртуальная память операционной системы исчерпана или ограничена адресным пространством 32-разрядного процесса, операционная система отклонит выделение собственной памяти;
    5. JVM броситjava.lang.OutOfMemoryError:Unableto createnewnativethreadОшибка.

5.3 Решения

  1. Найдите способ уменьшить количество потоков, создаваемых в программе, и проанализируйте, действительно ли приложению нужно создавать так много потоков.
  2. Если вам действительно нужно создать много потоков, увеличьте максимальное количество потоков на уровне ОС: выполнитьulimia-aЧтобы просмотреть ограничение на максимальное количество потоков, используйтеulimit-u xxxОтрегулируйте ограничение максимального количества потоков

6. Метапространство

Пространство Permgen появилось до JDK 1.8, эта ошибка указывает на то, что постоянное поколение (Permanent Generation) заполнено, обычно из-за того, что количество загруженных классов слишком велико или размер слишком велик. С отменой постоянной генерации в 1.8 этого исключения больше не будет.

Метапространство — это реализация области метода в HotSpot.Самая большая разница между ним и постоянным поколением заключается в том, что метапространство находится не в памяти виртуальной машины, а использует локальную память, но локальная память также заполнена, поэтому будут исключения .

6.1 Написать ошибку

/**
 * JVM Options: -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 */
public class MetaspaceOOMDemo {

    public static void main(String[] args) {

        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MetaspaceOOMDemo.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
                //动态代理创建对象
                return methodProxy.invokeSuper(o, objects);
            });
            enhancer.create();
        }
    }
}

Динамически создавать объекты с помощью Spring GCLib

Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError-->Metaspace

6.2 Решения

Переполнение области метода также является распространенным исключением из-за переполнения памяти.В сценариях приложений, в которых большое количество динамических классов генерируется во время частого выполнения, особое внимание следует уделять повторному использованию этих классов. В дополнение к вышеупомянутому расширению байт-кода GCLib и динамическому языку также распространены такие сценарии, как большое количество JSP или приложений, которые динамически генерируют файлы JSP (могут быть в традиционной индустрии программного обеспечения в древние времена), приложения на основе OSGi (даже если одни и те же файлы классов, загруженные разными загрузчиками, также будут рассматриваться как разные классы) и т. д.

Метод, как правило, менее подверженная региону в JDK8, точка доступа обеспечивает некоторые параметры для установки пространства элемента, могут играть роль в предотвращении

  • -XX:MaxMetaspaceSizeУстановите максимальное значение метапространства, по умолчанию -1, что означает отсутствие ограничений (оно все еще ограничено размером локальной памяти)
  • -XX:MetaspaceSizeУкажите начальный размер метапространства в байтах. При достижении этого значения сборщик мусора выполнит выгрузку типа, а сборщик одновременно скорректирует значение.
  • -XX:MinMetaspaceFreeRatioУправление минимальным процентом оставшейся емкости метапространства после сборки мусора может снизить частоту сборки мусора из-за нехватки метапространства и аналогичным образом.MaxMetaspaceFreeRatio

7. Запрошенный размер массива превышает лимит ВМ

7.1 Написать ошибку

public static void main(String[] args) {
  int[] arr = new int[Integer.MAX_VALUE];
}

Это относительно простое, и у него будет оом в создании супер-большой группы.

Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit

JVM ограничивает максимальную длину массива, что указывает на то, что массив запросов программы превышает максимальную предел длины.

Перед выделением памяти для массива JVM проверяет, является ли выделяемая структура данных адресуемой в системе, обычноInteger.MAX_VALUE-2.

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

8. Недостаточно места подкачки

При запуске приложения Java выделяется ограниченное количество памяти. Это ограничение указывается через -Xmx и другие подобные параметры запуска.

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

Эта ошибка означает, что вся доступная виртуальная память исчерпана. Виртуальная память состоит из физической памяти и пространства подкачки.

Я никогда не видел такой ошибки~~~

9. Убить процесс или пожертвовать ребенком

Операционные системы построены на концепции процессов. Эти процессы отвечают за несколько заданий ядра, одно из которых называется «Убийца нехватки памяти», которое «убивает» определенные процессы, когда доступной памяти становится крайне мало. OOM Killer оценивает все процессы, а затем "убивает" процессы с более низкими оценками. Конкретные правила оценки см. в разделе Работа с Linux OOM Killer.

В отличие от других ошибок OOM,Killprocessorsacrifice childОшибки возникают не на уровне JVM, а на уровне ОС.

9.1 Анализ причин

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

Однако такой подход неизбежно несет определенный риск «перепроданности». Например, некоторые процессы продолжают занимать системную память, а затем приводят к тому, что другие процессы не имеют свободной памяти. В этот момент система автоматически активирует OOM Killer, ищет процессы с низкими показателями и «убивает» их, чтобы освободить ресурсы памяти.

9.2 Решения

  • Обновите конфигурацию сервера/изолируйте развертывания, чтобы избежать конфликтов
  • Убийственный тюнинг OOM.

Наконец, прикрепите изображение бога «Я Хай».

涯海

Ссылка и спасибо

«Понимание виртуальной машины Java, 3-е издание»

PLU MBR.IO/недостаточно памяти…

В частности, Aliyun.com/articles/71…

GitHub.com/стабильность MA…