Резюме точек знаний JVM

Java JVM
Резюме точек знаний JVM

Эта статья является кратким изложением после изучения «Глубокого понимания виртуальной машины Java».Основное содержание взято из книги, а также есть некоторые понимания автора. Один - разобраться в знаниях и обобщить их, а другой - поделиться и сообщить, если есть ошибки, пожалуйста, укажите. (Эта статья основана на спецификации jdk1.7).

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

1. Область памяти JVM


Виртуальная машина Java во время выполнения пространство памяти будет разделено на несколько областей, в соответствии с заранее определенным «(Java SE Edition. 7) Спецификация виртуальной машины Java«, область памяти Java Virtual Machine разделена на следующие разделы: Район метода, память кучи, стек виртуальной машины, стеки нативного метода, счетчик программы.
这里写图片描述

1. Область метода

Область методов в основном используется для хранения таких данных, как информация о классе, константы, статические переменные, загруженные виртуальной машиной, и код, скомпилированный компилятором. В jdk1.7 и более ранних версиях область метода является «логической частью» кучи (непрерывное пространство кучи), но для того, чтобы отличить ее от кучи, область метода также имеет имя, называемое «не куча», и некоторые люди используют «постоянную кучу». Генерация (реализация области метода HotSpot) для представления области метода.

Начиная с jdk1.7, был подготовлен план "постоянной генерации". пул констант удаляет символы пул констант строк и пул констант классов и т. д.), здесь просто переместите пул строковых констант в память кучи; в jdk1.8 область методов больше не существует, информация о классе и данные скомпилированного кода хранятся в исходная область метода После перемещения в метапространство (MetaSpace) метапространство не находится в памяти кучи, а непосредственно занято локальной памятью (NativeMemory). По информации из интернета и по своему разумению, я нарисовал картину изменений в области методов в jdk1.3~1.6, jdk1.7 и jdk1.8 следующим образом (если есть какие-то необоснованные места, я надеюсь, читатели обратят внимание):
这里写图片描述
Причинами перехода на постоянную генерацию являются:
(1) Строка существует в постоянном поколении, которое подвержено проблемам с производительностью и переполнению памяти.
(2) Сложно определить размер информации о классе и методе, поэтому трудно указать размер постоянной генерации.Если она слишком мала, то легко переполнить постоянную генерацию, а если слишком большой, легко вызвать переполнение старого поколения.
(3) Постоянная генерация внесет ненужную сложность в сборщик мусора, а эффективность восстановления будет низкой.

2. Куча памяти

Память кучи в основном используется для хранения объектов и массивов.Это самая большая область памяти, управляемая JVM.Память кучи и область методов совместно используются всеми потоками и создаются при запуске виртуальной машины. С точки зрения сборки мусора, поскольку сборщики в основном используют алгоритм сбора поколений, куча также может быть разделена на YoungGeneration и OldGeneration, а новое поколение также может быть разделено на Eden, From Survivor, To Survivor.

3. Счетчик программ

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

4. Стек виртуальных машин

Виртуальная машина также является личным пространством памяти для каждого потока.Она описывает модель памяти метода.Непосредственно посмотрите на рисунок ниже:
这里写图片描述
Виртуальная машина выделяет стек виртуальной машины к каждому потоку, и каждый стек виртуальной машины имеет несколько кадров стека, и каждая рамка стека хранит локальную таблицу переменных, стек операнда, динамическую ссылку, обратный адрес и т. Д. Рамка стека соответствует методу в коде Java. Когда нить выполняет метод, это означает, что рамка стека, соответствующая этому способу, ввел стек виртуальной машины и находится в верхней части стека. Каждый метод Java вызывается из Время его вызывается до конца выполнения., что соответствует процессу рамы стека от толкания в стек.

5. Стек локальных методов

Разница между стеком собственных методов и стеком виртуальной машины заключается в том, что стек виртуальной машины выполняет методы Java, стек собственных методов выполняет собственные методы, а остальные в основном одинаковы.В HotSpot стек собственных методов и стек виртуальной машины связаны напрямую. Объединив два в одно, я пока не буду подробно описывать здесь.

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

Как упоминалось выше, в jdk1.8 отсутствует постоянное поколение (область методов), а пространство, которое заменяет его, называется «мета-пространство». Подобно постоянному поколению, это реализация спецификации JVM для области методов. , но метапространства там нет.В виртуальной машине вместо этого используется локальная память, и размер метапространства ограничен только локальной памятью, но размер метапространства может быть указан с помощью -XX:MetaspaceSize и - XX:МаксМетаспейсСизе.

2. Переполнение памяти JVM


1. Переполнение динамической памяти

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

/**
 * 设置最大堆最小堆:-Xms20m -Xmx20m
 * 运行时,不断在堆中创建OOMObject类的实例对象,且while执行结束之前,GC Roots(代码中的oomObjectList)到对象(每一个OOMObject对象)之间有可达路径,垃圾收集器就无法回收它们,最终导致内存溢出。
 */
public class HeapOOM {
    static class OOMObject {
    }
    public static void main(String[] args) {
        List<OOMObject> oomObjectList = new ArrayList<>();
        while (true) {
            oomObjectList.add(new OOMObject());
        }
    }
}

После запуска будет сообщено об исключении, а информация java.lang.OutOfMemoryError: пространство кучи Java будет отображаться в информации о стеке, что указывает на то, что в пространстве кучи памяти возникает исключение переполнения памяти.

Вновь сгенерированный объект изначально размещается в новом поколении.После того, как новое поколение будет заполнено, будет выполнен Minor GC.Если после Minor GC недостаточно места, объект и объекты, соответствующие условиям в новом поколении, будут быть помещены в старое поколение.Когда места в старом поколении недостаточно, будет выполнена полная сборка мусора.Если места недостаточно для хранения нового объекта, генерируется исключение OutOfMemoryError. Распространенные причины: в память загружается слишком много данных, например, слишком много данных из базы данных за один раз; слишком много ссылок на объекты в коллекции, которые не очищаются после использования; в коде присутствует бесконечный цикл или слишком цикл генерирует много повторяющихся объектов; выделение памяти в куче нецелесообразно; проблемы с сетевым подключением, проблемы с базой данных и т. д.

2. Переполнение стека виртуальной машины/локального стека методов

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

/**
 * 设置每个线程的栈大小:-Xss256k
 * 运行时,不断调用doSomething()方法,main线程不断创建栈帧并入栈,导致栈的深度越来越大,最终导致栈溢出。
 */
public class StackSOF {
    private int stackLength=1;
    public void doSomething(){
            stackLength++;
            doSomething();
    }
    public static void main(String[] args) {
        StackSOF stackSOF=new StackSOF();
        try {
            stackSOF.doSomething();
        }catch (Throwable e){//注意捕获的是Throwable
            System.out.println("栈深度:"+stackSOF.stackLength);
            throw e;
        }
    }
}

После выполнения приведенного выше кода выдается: Исключение в потоке "Thread-0" java.lang.StackOverflowError.

(2) OutofMeMoryError: если виртуальная машина на момент подачи заявления для расширения стека не может быть достаточно памяти, бросить OutOfMeMoryError. Мы можем понять, что виртуальная машина может использоваться для пространства на оккупированной стеке - доступная физическая память - максимальная память кучи - максимальная область области памяти, такая как память машины до 4G, системы и других приложений, занимающихся 2G, Виртуальная машина Доступная физическая память 2G, максимальная память кучи до 1 г, максимальная память для области 512M, что для хранения памяти стека составляет около 512 м, если мы настроим каждый размер стека нитей - 1 м, эта виртуальная машина может создать до 512 потоков, более 512 потоков могут затем создать никакой комнаты для укладки, оно сообщила о недооформеренности.
这里写图片描述
Пример ошибки OutOfMemoryError, которая может быть сгенерирована в стеке, выглядит следующим образом:

/**
 * 设置每个线程的栈大小:-Xss2m
 * 运行时,不断创建新的线程(且每个线程持续执行),每个线程对一个一个栈,最终没有多余的空间来为新的线程分配,导致OutOfMemoryError
 */
public class StackOOM {
    private static int threadNum = 0;
    public void doSomething() {
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        final StackOOM stackOOM = new StackOOM();
        try {
            while (true) {
                threadNum++;
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        stackOOM.doSomething();
                    }
                });
                thread.start();
            }
        } catch (Throwable e) {
            System.out.println("目前活动线程数量:" + threadNum);
            throw e;
        }
    }
}

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

Резюме: когда потоков мало, если глубина запроса потока слишком велика, будет сообщено об исключении StackOverflow.Чтобы решить эту проблему, вы можете соответствующим образом увеличить глубину стека (увеличить размер пространства стека), то есть установить значение -Xss на большее значение. , но в целом это скорее проблема кода; когда виртуальная машина генерирует поток, она не может обратиться за стековым пространством для потока, и возникает исключение OutOfMemoryError Чтобы решить эту проблему, можно соответствующим образом уменьшить глубину стека, то есть значение -Xss установить меньше, пространство, занимаемое каждым потоком, меньше, а общее пространство должно быть в состоянии вместить больше потоков, но операционная система имеет ограничение на количество потоков в процессе, и значение опыта составляет около 3000~5000. До jdk1.5 -Xss по умолчанию 256 КБ, после jdk1.5 по умолчанию 1M, этот параметр довольно жесткий для системы, и его следует устанавливать осторожно в соответствии с реальной ситуацией.

3. Переполнение области метода

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

Поскольку пул строковых констант существовал в области методов до jdk1.6, основанный на виртуальной машине до jdk1.6, могут непрерывно генерироваться несогласованные строки (в то же время необходимо убедиться, что существует достижимый путь между GC Roots и ) для имитации исключения OutOfMemoryError в области методов; однако в области методов также хранится загруженная информация о классе, поэтому виртуальная машина на основе jdk1.7 может имитировать переполнение области методов путем динамического создания большого количества классов.

/**
 * 设置方法区最大、最小空间:-XX:PermSize=10m -XX:MaxPermSize=10m
 * 运行时,通过cglib不断创建JavaMethodAreaOOM的子类,方法区中类信息越来越多,最终没有可以为新的类分配的内存导致内存溢出
 */
public class JavaMethodAreaOOM {
    public static void main(final String[] args){
       try {
           while (true){
               Enhancer enhancer=new Enhancer();
               enhancer.setSuperclass(JavaMethodAreaOOM.class);
               enhancer.setUseCache(false);
               enhancer.setCallback(new MethodInterceptor() {
                   @Override
                   public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                       return methodProxy.invokeSuper(o,objects);
                   }
               });
               enhancer.create();
           }
       }catch (Throwable t){
           t.printStackTrace();
       }
    }
}

После запуска приведенного выше кода будет сообщено об исключении «java.lang.OutOfMemoryError: PermGen space», указывающем на ошибку переполнения памяти в области метода.

4. Прямое переполнение памяти машины

Собственная прямая память (DirectMemory) не является частью области данных времени выполнения виртуальной машины или области памяти, определенной в спецификации виртуальной машины Java, но когда операции, связанные с NIO, используются в Java (например, применяется метод allocteDirect ByteBuffer). для собственной машины) прямая память), также может возникнуть исключение нехватки памяти.

3. Сборка мусора JVM


Сборка мусора заключается в очистке неиспользуемых объектов в памяти через сборщик мусора. Содержимое, участвующее в сборке мусора: 1. Определите, является ли объект мертвым, 2. Выберите алгоритм сборки мусора, 3. Выберите время сборки мусора, 4. Выберите соответствующий сборщик мусора для очистки мусора (мертвых объектов).

1. Определить, мертв ли ​​объект

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

(1) Алгоритм подсчета ссылок

Добавьте счетчик ссылок к каждому объекту.Всякий раз, когда есть место для ссылки на него, значение счетчика увеличивается на 1; всякий раз, когда есть место, которое больше не ссылается на него, значение счетчика уменьшается на 1, поэтому до тех пор, пока значение счетчика не равно 0, это означает, что на него еще есть место для ссылки, и это не бесполезный объект. Как показано на рисунке ниже, объект 2 имеет 1 ссылку и значение его счетчика ссылок равно 1, а объект 1 имеет две локальные ссылки и значение его счетчика ссылок равно 2.
这里写图片描述

Этот метод кажется очень простым, но многие массовые виртуальные машины не используют этот алгоритм для управления памятью. Причина в том, что, когда некоторые объекты ссылаются друг на друга, невозможно судить, мертвы ли эти объекты. Как показано на рисунке ниже, на объект 1 и объект 2 не ссылаются переменные вне кучи, а ссылаются друг на друга.В это время, хотя они и бесполезны, значение счетчика ссылок по-прежнему равно 1. Нельзя судить, что они мертвы объекты, а сборщик мусора не может.

这里写图片描述

(2) Алгоритм анализа достижимости

Прежде чем понять алгоритм анализа достижимости, сначала разберитесь с концепцией — корни GC, отправная точка сборки мусора, которые можно использовать в качестве корней GC с объектами, на которые ссылаются в таблице локальных переменных в стеке виртуальной машины, объектами, на которые ссылаются статические атрибуты в стеке виртуальной машины. область методов и области методов Объект, на который ссылается константа в локальном стеке методов, объект, на который ссылается JNI (собственный метод) в локальном стеке методов.
Когда у объекта нет цепочки ссылок, связанной с GC Roots (GC Roots недоступен для этого объекта), это означает, что этот объект недоступен и является мертвым объектом. Как показано на рисунке ниже: Существуют достижимые пути между объектами object1, object2, object3, object4 и GC Roots.Эти объекты не будут переработаны, но object5, object6 и object7 достигнут GC. При отсутствии доступных путей между корнями эти объекты приговариваются к смерти.
这里写图片描述
Объекты, приговоренные к смерти выше (объект 5, объект 6, объект 7), не обязательно умрут, и есть место для спасения. После выполнения анализа достижимости, когда между объектом и корнями GC отсутствует цепочка ссылок, объект будет помечен один раз, а затем будет оцениваться, не переопределяет ли объект метод finalize() объекта или метод метод finalize() был вызван виртуальной машиной, то они будут выполнены (очищены); если объект переопределяет метод finalize() и не был вызван, то будет выполнено содержимое метода finalize(), поэтому в методе finalize(), если Корни могут спасти себя, обратившись к ассоциации объектов в цепочке, но, как правило, это делать не рекомендуется.Учитель Чжоу Чжимин также предположил, что каждый может полностью забыть об этом методе~

(3) Способ рециркуляции площадей

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

  • Все экземпляры этого класса были утилизированы (экземпляров этого класса в куче нет)

  • ClassLoader, загружавший класс, был переработан.

  • Объект java.lang.Class, соответствующий этому классу, нигде не упоминается (к методам этого класса нельзя получить доступ через отражение)

2. Часто используемые алгоритмы сборки мусора

Существует три широко используемых алгоритма сборки мусора: пометка-очистка, копирование и пометка-сортировка.

(1) Алгоритм маркировки-очистки: он разделен на два этапа: маркировка и очистка. Сначала помечаются все объекты, которые необходимо переработать. После завершения маркировки все помеченные объекты восстанавливаются равномерно, как показано на следующем рисунке. .

Недостатки: Два процесса пометки и очистки неэффективны, после пометки и очистки будет генерироваться большое количество прерывистых фрагментов памяти.
这里写图片描述

(2) Алгоритм копирования: Разделите память на два блока одинакового размера и используйте только один из них для каждого хранилища.Когда этот блок израсходован, все выжившие объекты копируются в другой блок, а использованная память сохраняется в Все пространство очищается, и цикл повторяется, как показано на рисунке ниже.

Недостаток: фактическое используемое пространство памяти уменьшено до половины от исходного, что более удобно.
这里写图片描述

(3) Маркировка - Алгоритм Squire: сначала пометьте доступные объекты, а затем все отмеченные объекты переместятся в абзац, наконец, очистите память, кроме границы доступного объекта, как показано ниже.
这里写图片描述

(4) Алгоритм сбора поколений: память кучи делится на новое поколение и старое поколение, а новое поколение дополнительно разделено на район Эдена, от живых и выживших. Как правило, объекты в новом генерировании в основном рождаются и умирают, и каждый раз выживает только небольшое количество объектов. Следовательно, используя алгоритм репликации, необходимо скопировать только небольшое количество выживших объектов, необходимо скопировать для полной сборки мусора; объекты в Старое поколение имеет более высокую скорость выживаемости. Алгоритмы отметки и сортировки отметки используются для переработки.
这里写图片描述

Вывоз мусора на этих участках может иметь следующие ситуации:

  • В большинстве случаев новые объекты размещаются в области Эдема.Когда в области Эдема нет места для размещения, будет выполняться Minor GC для очистки бесполезных объектов в области Эдема. После очистки, если уцелевшие объекты в Эдеме и Из Выжившего меньше, чем доступное пространство В Выживший, они попадут в Выжившего, в противном случае попадут в старость напрямую); возраст объектов, еще выживших в Эдеме и Из Выжившего и Способность войти в «Выживший» увеличивается на 1 год (виртуальный возраст).Машина определяет счетчик возраста для каждого объекта, и возраст Малого ГК увеличивается на 1 при каждом его выполнении.Когда возраст выжившего объекта достигает определенного уровня (по умолчанию 15 лет), он входит в старость.Вы можете установить значение возраста с помощью -XX:MaxTenuringThreshold .

  • При выполнении Minor GC Eden не хватает места для выделения места под новый объект (новый объект должен быть очень большим), и новый объект сразу входит в старость.

  • Объекты, занимающие больше половины пространства До выжившего и одного возраста, объекты с возрастом больше или равным этому возрасту сразу входят в старость.Например, пространство Уцелевший 10М, а несколько объектов с возрастом из 4 занимают общее пространство более 5М, тогда возраст больше или равен 4. Объекты переходят в старость напрямую, не дожидаясь возраста, указанного MaxTenuringThreshold.

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

  • Когда прямой вызов System.gc () в коде Java рекомендовал бы полный сборщик мусора JVM, но, как правило, запускает полный сборщик мусора, как правило, не рекомендуется, попробуйте создать свою собственную стратегию управления сборщиком мусора виртуальными машинами.

  • Постоянная генерация (область методов) используется для хранения информации о классе. Постоянная генерация jdk1.6 и более ранних версий также хранит константы, статические переменные и т. д. Когда места постоянной генерации недостаточно, также будет запущен полный сборщик мусора. потребность в хранении новых данных в постоянном поколении не может быть удовлетворена, будет выдано исключение переполнения памяти постоянного поколения.

  • Большие объекты (объекты, которым требуется много непрерывной памяти), такие как очень длинные массивы, будут напрямую попадать в старую память.Если в старости недостаточно непрерывного большого пространства для хранения, будет выполняться полная сборка мусора.

3. Выберите время сбора мусора

Когда программа работает, все время меняются всевозможные данные, объекты, потоки, память и т. д. Когда выдается команда сборки мусора, он собирается сразу? Конечно нет. Вот два понятия: безопасная точка и безопасная область.
Безопасная точка: с точки зрения потоков безопасные точки можно понимать как некоторые особые позиции в процессе выполнения кода.Когда поток выполняется до безопасной точки, это означает, что текущее состояние виртуальной машины безопасно.При необходимости, Вы можете нажать здесь Приостановить обсуждение пользователя. При сборке мусора, если вам нужно приостановить текущий пользовательский поток, но пользовательский поток в данный момент не находится в безопасной точке, перед приостановкой следует дождаться выполнения этих потоков в безопасной точке. Например, мать подметает пол, а сын ест арбуз (корка дыни будет брошена на землю), когда мать подметает перед сыном, сын говорит: «Подожди, дай мне закончи этот кусок, прежде чем подметать.» После того, как арбузная корка брошена на землю, это точка безопасности, и мать может продолжать подметать пол (уборщик мусора может продолжать собирать мусор). Теоретически точка сохранения может быть размещена на границе каждого байт-кода интерпретатора, но на практике точка сохранения в основном выбирается на основе критерия «имеет ли она характеристики, позволяющие программе выполняться в течение длительного времени».
Безопасная область: безопасная точка относится к работающему потоку. Для потоков в таких состояниях, как спящий режим или блокировка, сборщик не будет ждать, пока этим потокам будет выделено процессорное время. В это время, пока поток находится в безопасном области, ее можно рассматривать как безопасную зону. Безопасная область — это фрагмент кода, в котором отношение ссылок не изменится, его можно рассматривать как расширенную и вытянутую безопасную точку. Приведенный пример также используется для иллюстрации того, что мать подметает пол, а сын ест арбуз (корка дыни будет брошена на землю). Когда мать подметает перед сыном, сын сказал: " Мама, продолжай подметать пол, я должен поесть за 10 минут!» Время, когда сын ест дыни, является безопасной зоной, а мать может продолжать подметать пол (уборщик мусора может продолжать собирать мусор).

4. Обычные сборщики мусора

Наиболее распространены следующие типы сборщиков мусора:

Коллекционеры нового поколения: Serial, ParNew, Parallel Scavenge

Сборщики старого поколения: Serial Old, CMS, Parallel Old

Сборщик мусора в памяти кучи: G1

Между сборщиками мусора каждого типа есть провода, указывающие на то, что их можно использовать вместе.
这里写图片描述

(1) Серийный коллектор

Serial - это одноретичный коллектор для нового поколения, используя алгоритмы репликации для сборки мусора. Когда сериал собирает сборку мусора, не только с потоком выполняет сбор мусора, он собирается, и все пользовательские потоки должны быть приостановлены (остановить мир). Например, когда мать очищает здоровье дома, она определенно не подумает, что сын бросит конфетти на землю, в противном случае он создаст мусор, очистить мусор и снова, это не закончено.
Ниже приведена схема комбинации последовательного сборщика и последовательного старого сборщика для сборки мусора.Когда все пользовательские потоки выполняются до безопасной точки, все потоки приостанавливают выполнение. , пользовательский поток продолжает выполняться.
这里写图片描述

Применимые сценарии: клиентский режим (настольное приложение); одноядерный сервер. Серийный номер можно выбрать в качестве коллектора молодого поколения с помощью -XX:+UserSerialGC.

2) коллектор ParNew

ParNew — это многопоточная версия Serial, а остальные ничем не отличаются от Serial. ParNew не дает лучших результатов, чем сборщик Serial в одноядерной среде ЦП.Количество потоков сбора, которые он открывает по умолчанию, совпадает с количеством ЦП.Вы можете установить количество потоков сбора мусора с помощью -XX:ParallelGCThreads .
Ниже приведена схематическая диаграмма комбинации сборщика ParNew и сборщика Serial Old для сборки мусора. Когда все пользовательские потоки выполняются до безопасной точки, все потоки приостанавливают выполнение. Сборщик ParNew использует несколько потоков и алгоритм репликации. для сбора мусора. , пользовательский поток продолжает выполняться.
这里写图片描述
Применимые сценарии: многоядерные серверы, используемые с коллекторами CMS. Когда -XX:+UserConcMarkSweepGC используется для выбора CMS в качестве сборщика старого поколения, сборщиком нового поколения по умолчанию является ParNew.Можно также использовать -XX:+UseParNewGC, чтобы указать использование ParNew в качестве сборщика нового поколения.

(3) Параллельный сборщик мусора

Parallel Scavenge также является многопоточным сборщиком нового поколения.Отличие от ParNew в том, что цель ParNew — максимально сократить время паузы пользовательских потоков во время сборки мусора.Цель Parallel Scavenge — достичь контролируемая пропускная способность количество. Пропускная способность — это отношение времени выполнения ЦП пользовательских потоков к общему времени выполнения ЦП [пропускная способность = время выполнения пользовательского кода / (время выполнения пользовательского кода + время сборки мусора)], например, виртуальная машина работает в течение всего 100 минут, в которых сбор мусора занял 1 минуту, значит пропускная способность 99%. Например, в следующих двух сценариях сборщик мусора производит сбор каждые 100 секунд с паузой в 10 секунд, а сборщик мусора – каждые 50 секунд с паузой в 7 секунд. пропускная способность становится ниже, и общая загрузка ЦП становится ниже.

Частота сбора время паузы пропускная способность
Собирать каждые 100 секунд 10 секунд 91%
Собирать каждые 50 секунд 7 секунд 88%

Вы можете использовать -XX:MaxGCPauseMillis, чтобы указать, сколько времени требуется сборщику для максимально возможного завершения высвобождения памяти, и вы можете использовать -XX:GCTimeRatio, чтобы точно контролировать пропускную способность.

Ниже приведена схема комбинации сборщика Parallel и сборщика Parallel Old для сборки мусора. В новом поколении, когда все пользовательские потоки выполняются до безопасной точки, все потоки приостанавливают выполнение. Сборщик ParNew использует несколько потоков и использует алгоритм репликации для сбора мусора. , после сборки пользовательский поток продолжает выполняться; в старости, когда пользовательский поток выполняется до безопасной точки, все потоки приостанавливают выполнение, а сборщик Parallel Old использует многопоточность и алгоритм сортировки тегов для выполнения работы по сбору мусора.
这里写图片描述
Применимые сценарии: сосредоточьтесь на пропускной способности, эффективно используйте ЦП, требуйте эффективных операций и не требуйте большого взаимодействия. Вы можете использовать -XX:+UseParallelGC, чтобы выбрать Parallel Scavenge в качестве сборщика нового поколения.По умолчанию jdk7 и jdk8 используют Parallel Scavenge в качестве сборщика нового поколения.

(4) серийный старый коллекционер

Сборщик Serial Old — это старая версия Serial, которая также является однопоточным сборщиком и использует алгоритм маркировки для сопоставления.

На следующем рисунке схематически показана комбинация сборщика Serial и сборщика Serial Old для сборки мусора:
这里写图片描述
Сценарий приложения: клиентский режим (настольное приложение), одноядерные серверы, сборщик Parallel Scavenge с сборщиком CMS в качестве резервного плана.

(5) Коллектор CMS (Concurrent Mark Sweep)

Сборщик CMS — это сборщик, нацеленный на кратчайшее время паузы повторного использования, известное как «кратчайшее время паузы пользовательского потока». Весь процесс сборки мусора делится на 4 этапа:

① Начальная отметка: отметьте объекты, с которыми GC Roots может напрямую ассоциироваться, что быстрее.
② Параллельная маркировка: GC Roots Tracing выполняется для маркировки всех мусорных объектов, что занимает много времени.
③ Повторная маркировка: исправьте записи маркировки объектов, которые изменились из-за того, что программа пользователя продолжает работать на параллельной фазе маркировки, что занимает меньше времени.
④ Параллельное удаление: используйте алгоритм пометки для очистки для удаления объектов мусора, что занимает много времени.

Самая продолжительная по времени параллельная маркировка и одновременная очистка всего процесса — все это работа с пользовательскими потоками, поэтому в целом сборщик мусора CMS можно рассматривать как выполняемый одновременно с пользовательскими потоками.
这里写图片描述
В сборщике CMS также есть некоторые недостатки:

  • Чувствителен к ресурсам ЦП: количество потоков сборки мусора, выделенных по умолчанию, равно (количество ЦП + 3)/4.По мере уменьшения количества ЦП, чем больше ресурсов ЦП занято, тем меньше пропускная способность.

  • Невозможно обработать плавающий мусор: на этапе параллельной очистки, поскольку пользовательский поток все еще выполняется и постоянно генерируется новый мусор, сборщик CMS не может очистить эту часть мусора в текущей коллекции. В то же время, поскольку пользовательские потоки также выполняются одновременно на этапе сборки мусора, сборщик CMS не может ждать, пока заполнится старое время, прежде чем собирать, как другие сборщики, и ему необходимо зарезервировать некоторое пространство для запуска и выполнения пользовательских потоков. использовать. Когда CMS работает, зарезервированное пространство памяти не может удовлетворить потребности пользовательских потоков, и возникает ошибка «Сбой параллельного режима». В это время будет активирован план резервного копирования, и Serial Old будет временно использоваться для восстановления собирать мусор старого поколения.

  • Так как CMS основан на алгоритме маркировки-очистки, после сборки мусора будет сгенерирована фрагментация пространства. Вы можете включить дефрагментацию с помощью -XX:UserCMSCompactAtFullCollection (включено по умолчанию).Перед тем, как CMS выполнит полный сборщик мусора, фрагментация памяти будет устранена . Вы также можете использовать -XX:CMSFullGCsBeforeCompaction, чтобы указать, сколько раз выполнять полный сборщик мусора без сжатия (без дефрагментации), за которым следует полный сборщик мусора со сжатием (дефрагментация). Применимые сценарии: обратите внимание на скорость ответа сервера и потребуйте от системы паузы на самое короткое время. Вы можете использовать -XX:+UserConMarkSweepGC, чтобы выбрать CMS в качестве коллектора старого поколения.

(6) Параллельный старый коллектор

Сборщик Parallel Old — это старая версия Parallel Scavenge, многопоточного сборщика, использующего алгоритм маркировки для сопоставления. Его можно сочетать с коллектором Parallel Scavenge, чтобы в полной мере использовать вычислительную мощность многоядерных процессоров.
这里写图片描述
Применимые сценарии: используйте с коллектором Parallel Scavenge, сосредоточьтесь на пропускной способности. jdk7 и jdk8 по умолчанию используют этот сборщик как старый сборщик и используют -XX:+UseParallelOldGC, чтобы указать использование старого сборщика Parallel.

(7) коллектор G1

Сборщик G1 — это коммерческий сборщик, на который официально ссылается jdk1.7, и теперь он стал сборщиком по умолчанию для jdk9. Областью действия предыдущих сборщиков является новое поколение или старое поколение.Областью сбора мусора G1 является вся память кучи.Он принимает идею "разбить все на ноль" и делит всю память кучи на несколько независимых объектов одинакового размера.Регион, концепции нового поколения и старого поколения по-прежнему сохраняются в коллекторе G1, который является частью региона соответственно, как показано на следующем рисунке:
这里写图片描述
Каждый блок представляет собой область, каждая область может быть Эдемом, Выжившим, Старостью, и номер каждой области не обязателен. Когда JVM запускается, она автоматически устанавливает размер каждой области (1M~32M, что должно быть степенью 2), и можно установить максимум 2048 областей (то есть максимальный поддерживаемый размер кучи составляет 32M*2048). =64G). Если вы установите -Xmx8g - Xms8g, размер каждой области составит 8g/2048=4M.

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

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

Как показано на рисунке ниже, процесс сбора сборщиком G1 включает начальную маркировку, параллельную маркировку, окончательную маркировку, а также отсеивание и переработку, что очень похоже на процесс сбора первых нескольких шагов сборщика CMS:
这里写图片描述
① Начальная отметка: отметьте объекты, непосредственно связанные с корнями GC. Этот этап быстрая, и пользовательская нить должна быть остановлена ​​и выполнена в одном потоке.
② Параллельная маркировка: начиная с GC Root, выполните новый анализ достижимости объектов в куче, чтобы найти выжившие объекты.Этот этап занимает много времени, но его можно выполнять одновременно с пользовательскими потоками.
③ Окончательная метка: исправьте запись метки, из-за которой пользовательская программа выполнялась в фазе параллельной метки.
④ Проверка и переработка: на этапе проверки и переработки будет сортироваться стоимость и стоимость переработки каждого региона, а также указан план утилизации в соответствии с ожидаемым пользователем временем паузы GC (используйте наименьшее время для переработки региона, содержащего наибольшее количество мусора, что является источником Garbage First — блок с наибольшим количеством мусора очищается в первый раз), чтобы повысить эффективность повторного использования, метод параллельного выполнения с пользовательским потоком не используется, но пользовательский поток остановился.

Применимые сценарии: приложения, требующие максимально возможного контролируемого времени паузы сборщика мусора; приложения с большим объемом памяти. Вы можете использовать сборщик G1 с -XX:+UseG1GC, а jdk9 использует сборщик G1 по умолчанию.

В-четвертых, настройка производительности JVM.


1. Цель настройки JVM: использовать меньший объем памяти для достижения более высокой пропускной способности или меньшей задержки.

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

Вот еще несколько важных показателей:

  • Использование памяти: объем памяти, необходимый программе для нормальной работы.

  • Задержка: время паузы программы из-за сборки мусора.

  • Пропускная способность: отношение времени выполнения пользовательской программы к общему времени, затраченному пользовательской программой и сборкой мусора.

Конечно, как и принцип CAP, невозможно одновременно удовлетворить программу с небольшим объемом памяти, низкой задержкой и высокой пропускной способностью.Цели программы разные, и направления, которые следует учитывать при настройке, также разные. , Перед настройкой необходимо объединить фактические сценарии, установить четкие цели оптимизации, найти узкие места в производительности, целенаправленно оптимизировать узкие места и, наконец, протестировать и использовать различные инструменты мониторинга, чтобы подтвердить, соответствуют ли оптимизированные результаты целям.

2. Инструменты настройки JVM

(1) На настройку можно положиться, эталонные данные содержат журнал работы системы, сообщение об ошибке стека, журнал GC, моментальный снимок потока, моментальный снимок кучи и т. д.

1 Журнал запуска системы: работа системы печатается в коде программы, описывается траектория запуска системы на уровне кода (метод, доход, возвращаемое значение и т. д.), общая система имеет проблемы, система, в которой выполняется журнал, является первой Просмотр журнала.

② Информация об ошибке стека: когда в системе возникает исключение, вы можете предварительно локализовать проблему в соответствии с информацией о стеке. переполнение; по "java.lang.StackOverflowError" можно судить, что это переполнение стека; по "java.lang.OutOfMemoryError: пространство PermGen" можно судить, что это переполнение области метода и т. д.

③ Журнал GC: при запуске программы используйте -XX:+PrintGCDetails и -Xloggc:/data/jvm/gc.log для записи подробного процесса gc во время работы программы или напрямую настройте "-verbose:gc" параметр для размещения gc. Журнал выводится на консоль, и частота и время gc каждой области памяти могут быть проанализированы с помощью записанного gc-лога, чтобы найти проблемы и выполнить целевую оптимизацию. Например, следующий журнал GC:

2018-08-02T14:39:11.560-0800: 10.171: [GC [PSYoungGen: 30128K->4091K(30208K)] 51092K->50790K(98816K), 0.0140970 secs] [Times: user=0.02 sys=0.03, real=0.01 secs] 
2018-08-02T14:39:11.574-0800: 10.185: [Full GC [PSYoungGen: 4091K->0K(30208K)] [ParOldGen: 46698K->50669K(68608K)] 50790K->50669K(98816K) [PSPermGen: 2635K->2634K(21504K)], 0.0160030 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
2018-08-02T14:39:14.045-0800: 12.656: [GC [PSYoungGen: 14097K->4064K(30208K)] 64766K->64536K(98816K), 0.0117690 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] 
2018-08-02T14:39:14.057-0800: 12.668: [Full GC [PSYoungGen: 4064K->0K(30208K)] [ParOldGen: 60471K->401K(68608K)] 64536K->401K(98816K) [PSPermGen: 2634K->2634K(21504K)], 0.0102020 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 

Выше приведено в общей сложности 4 журнала GC, посмотрите на первую строку журналов, «2018-08-02T14:39:11.560-0800» — универсальный стандартный формат времени UTC с точностью до миллисекунды, настроенный с помощью «-XX: +PrintGCDateStamps" этот параметр может следовать за журналом gc, чтобы распечатать эту метку времени, "10,171" — это количество секунд, прошедших с момента запуска JVM до появления gc. «[GC» в начале первой строки текста журнала указывает, что Stop-The-World (пауза пользовательского потока) не происходила в этом GC, а «[Full GC» в начале второй строки текста журнала указывает что Stop-The-World произошел в этом GC. Мир, так сказать, [GC и [Полный GC не имеет ничего общего с новым и старым поколением, но имеет какое-то отношение к типу сборщика мусора.Если вы вызовете System.gc() напрямую, он отобразит [Full GC(System). Следующие «[PSYoungGen» и «[ParOldGen» представляют область, в которой происходит сборка мусора, а конкретное отображаемое имя также связано со сборщиком мусора. Например, «[PSYoungGen» здесь представляет сборщик Parallel Scavenge, а «[ParOldGen» представляет коллектор Serial Old. Кроме того, коллектор Serial отображает «[DefNew», коллектор ParNew отображает «[ParNew» и т. д. Следующее «30128K->4091K(30208K)» указывает на то, что после выполнения этого gc используемое пространство памяти в этой области уменьшается с 30128K до 4091K, а общий размер памяти составляет 30208K. "51092К->50790К(98816К), "51092К->50790К(98816К), 0,0140970 с" после этой сборки мусора, использование всей памяти кучи уменьшается с 51092 КБ до 50790 КБ, общий объем памяти кучи составляет 98816 КБ, а сборщик мусора занимает 0,0140970 секунд.

④ Снимок потока: как следует из названия, вы можете увидеть состояние потока в определенный момент в соответствии с снимком потока.Когда в системе могут быть тайм-ауты запросов, бесконечные циклы, взаимоблокировки и т. д., вы можете дополнительно определить проблема согласно снимку темы. Выполнив команду "jstack pid", которая поставляется с виртуальной машиной, вы можете сделать дамп информации о снимке потока в текущем процессе. В Интернете есть много примеров для более подробного использования и анализа. Эта статья была написана здесь для давно, так что особо описывать не буду. , выложу в блог для ознакомления:woo woo woo.cn blog on.com/Kong Zhongqi…

⑤ Моментальный снимок дампа кучи: при запуске программы вы можете использовать "-XX:+HeapDumpOnOutOfMemory" и "-XX:HeapDumpPath=/data/jvm/dumpfile.hprof". дамп (вы также можете напрямую использовать команду jmap для создания дампа снимка памяти в любой момент работы программы), а затем проанализировать использование памяти в это время.

(2) Инструменты настройки JVM

① Используйте jps (состояние процесса JVM) для просмотра всех процессов, запущенных виртуальной машиной, полного имени основного класса выполнения и параметров запуска JVM, например, когда выполняется основной метод в классе JPSTest (основной метод продолжает выполняться), выполните jps -l Вы можете видеть, что pid класса JPSTest ниже равен 31354, и вы также можете увидеть параметры запуска JVM с параметром -v.

3265 
32914 sun.tools.jps.Jps
31353 org.jetbrains.jps.cmdline.Launcher
31354 com.danny.test.code.jvm.JPSTest
380 

② Используйте jstat (инструмент мониторинга статистики JVM) для мониторинга информации о виртуальной машине jstat -gc pid 500 10 : вывод состояния кучи Java (емкость каждой области, используемая емкость, время сбора мусора и т. д.) каждые 500 миллисекунд и печать 10 раз.

S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
11264.0 11264.0 11202.7  0.0   11776.0   1154.3   68608.0    36238.7     -      -      -      -        14    0.077   7      0.049    0.126
11264.0 11264.0 11202.7  0.0   11776.0   4037.0   68608.0    36238.7     -      -      -      -        14    0.077   7      0.049    0.126
11264.0 11264.0 11202.7  0.0   11776.0   6604.5   68608.0    36238.7     -      -      -      -        14    0.077   7      0.049    0.126
11264.0 11264.0 11202.7  0.0   11776.0   9487.2   68608.0    36238.7     -      -      -      -        14    0.077   7      0.049    0.126
11264.0 11264.0  0.0    0.0   11776.0   258.1    68608.0    58983.4     -      -      -      -        15    0.082   8      0.059    0.141
11264.0 11264.0  0.0    0.0   11776.0   3076.8   68608.0    58983.4     -      -      -      -        15    0.082   8      0.059    0.141
11264.0 11264.0  0.0    0.0   11776.0    0.0     68608.0     390.0      -      -      -      -        16    0.084   9      0.066    0.149
11264.0 11264.0  0.0    0.0   11776.0    0.0     68608.0     390.0      -      -      -      -        16    0.084   9      0.066    0.149
11264.0 11264.0  0.0    0.0   11776.0   258.1    68608.0     390.0      -      -      -      -        16    0.084   9      0.066    0.149
11264.0 11264.0  0.0    0.0   11776.0   3012.8   68608.0     390.0      -      -      -      -        16    0.084   9      0.066    0.149

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

③ Используйте jmap (карта памяти для Java) для просмотра информации о памяти кучи.Выполните команду jmap -histo pid, чтобы распечатать количество экземпляров и использование памяти каждого класса в текущей куче, как показано ниже, имя класса — это имя класса каждого class ([B — тип byte, [C — тип char, [I — тип int]), bytes — это размер памяти всех экземпляров этого класса, а instances — количество экземпляров этого класса:

num     #instances         #bytes  class name
----------------------------------------------
   1:          2291       29274080  [B
   2:         15252        1961040  <methodKlass>
   3:         15252        1871400  <constMethodKlass>
   4:         18038         721520  java.util.TreeMap$Entry
   5:          6182         530088  [C
   6:         11391         273384  java.lang.Long
   7:          5576         267648  java.util.TreeMap
   8:            50         155872  [I
   9:          6124         146976  java.lang.String
  10:          3330         133200  java.util.LinkedHashMap$Entry
  11:          5544         133056  javax.management.openmbean.CompositeDataSupport

Выполните команду jmap -dump, чтобы сбросить снимок памяти кучи в указанный файл, например, выполните jmap -dump:format=b,file=/data/jvm/dumpfile_jmap.hprof 3361, чтобы сбросить текущий снимок памяти кучи в файл dumpfile_jmap.hprof. file , снимок памяти может быть проанализирован.

④ Используйте jconsole и jvisualvm для анализа информации о памяти (изменения памяти в различных областях, таких как Eden, Survivor, Old и т. д.), если вы просматриваете JVM удаленного сервера, вам необходимо добавить следующие параметры в запуск программы:

"-Dcom.sun.management.jmxremote=true" 
"-Djava.rmi.server.hostname=12.34.56.78" 
"-Dcom.sun.management.jmxremote.port=18181" 
"-Dcom.sun.management.jmxremote.authenticate=false" 
"-Dcom.sun.management.jmxremote.ssl=false"

На следующем рисунке показан интерфейс jconsole.В опции «Обзор» можно наблюдать за использованием памяти кучи, количеством потоков, количеством загрузок классов и использованием ЦП; в опции «Память» можно просмотреть использование памяти каждой области в куче и подробные описание в левом нижнем углу (объем памяти, ситуация с сборщиком мусора) и т. д.); параметр потока позволяет просматривать потоки, загруженные текущей JVM, просматривать информацию о стеке каждого потока, а также обнаруживать тупиковые ситуации; схема VM описывает различные подробные параметры виртуальной машины. (демонстрация функции jconsole)
这里写图片描述

На следующем рисунке показан интерфейс jvisualvm, который немного богаче, чем jconsole, но для большинства функций требуется установка плагинов. Обзор аналогичен обзору виртуальной машины jconsole, описывающему подробные параметры jvm и параметры запуска программы; дисплей мониторинга аналогичен обзорному интерфейсу jconsole (процессор, область кучи/метода, загрузка классов, поток); поток аналогичен интерфейсу потока jconsole. ; Семплер может отображать ранжированный список классов, занимающих в настоящее время память, и количество экземпляров; Visual GC может отображать размер занятости памяти и историческую информацию о каждой текущей области более подробно (ниже). (демонстрация функции jvisualvm)
这里写图片描述

5 Проанализируйте снимок дампа сваи

Как упоминалось ранее, параметр "-XX:+HeapDumpOnOutOfMemory" можно настроить для вывода текущего снимка памяти при переполнении программы. Вы также можете использовать команду jmap для вывода информации снимка текущего состояния памяти в любое время. снимок памяти, как правило, .hprof — это файл двоичного формата с суффиксом. Вы можете напрямую использовать команду jhat (инструмент анализа кучи JVM) для анализа снимка памяти, Суть ее фактически встраивает микросервер, и вы можете анализировать соответствующий снимок памяти через браузер, например, выполняя jhat -port 9810 -J-Xmx4G /данные/jvm/dumpfile_jmap.hprof Указывает на запуск встроенного сервера jhat через порт 9810:

Reading from /Users/dannyhoo/data/jvm/dumpfile_jmap.hprof...
Dump file created Fri Aug 03 15:48:27 CST 2018
Snapshot read, resolving...
Resolving 276472 objects...
Chasing references, expect 55 dots.......................................................
Eliminating duplicate references.......................................................
Snapshot resolved.
Started HTTP server on port 9810
Server is ready.

В консоли видно, что сервер запущен, заходимhttp://127.0.0.1:9810/Вы можете увидеть результаты анализа каждого класса на снимке (интерфейс немного низковат).На следующем рисунке представлена ​​информация о случайно выбранном мной классе, включая родительский класс этого класса, загрузчик классов, который загружает этот класс, и размер занимаемого места, также есть каждый экземпляр (ссылки) этого класса и его адрес памяти и размер ниже.Нажатие отобразит некоторые переменные-члены этого экземпляра и другую информацию:
这里写图片描述

jvisualvm также может анализировать снимки памяти.В меню jvisualvm "Файл"-"Загрузить" выберите снимки кучи памяти, и информация в снимках будет отображаться в графическом интерфейсе.Таким образом, вы можете в основном просматривать пространство, занимаемое каждый класс и экземпляр Количество и детали экземпляра и т. д.:
这里写图片描述

Существует также множество сторонних инструментов для анализа моментальных снимков памяти, таких как eclipse mat, который является более профессиональным, чем jvisualvm.В дополнение к просмотру пространства и количества, занимаемого каждым классом и его соответствующим экземпляром, вы также можете запросить цепочку вызовов между объекты, цепочка между инстансом и GC Root и т.д. Плагин mat можно установить в eclipse или скачать отдельную версию (woohoo.eclipse.org/toilet/down ОА…), после того, как я установил его на mac, он застрял.Следующий скриншот на окнах (Демонстрация функции MAT):

这里写图片描述

(3) Опыт настройки JVM

С точки зрения конфигурации JVM, как правило, сначала можно использовать конфигурацию по умолчанию (базовые начальные параметры могут обеспечить относительно стабильную работу общего приложения).В тесте в соответствии с рабочим статусом системы (параллелизм сеанса, время сеанса и т. д.). ), в сочетании с логами gc, Следует внести разумные коррективы в мониторинг памяти и используемый сборщик мусора.При слишком малом объеме памяти старого поколения может происходить частый Full GC, а при слишком большом объеме памяти - время Full GC будет особенно долгим.

Итак, какова наиболее подходящая конфигурация для JVM, например, новое поколение или старое поколение? Ответ не обязательно.Настройка - это процесс поиска ответа.Когда физическая память фиксирована, чем больше параметр нового поколения, тем меньше старое поколение, тем выше частота полного GC, но тем короче время полного GC; напротив, настройка нового поколения. Чем меньше значение, тем больше старость, тем ниже частота полного GC, но тем дольше время, затрачиваемое на каждый Full GC. предложения ниже:

  • Значение -XMS и -XMX установлено равным, размер кучи дефрарен, чтобы указать размер -XMS, а незанятая куча по умолчанию меньше 40%, JVM расширит размер указанного -XMX; когда незанятая память стека превышает 70%, JVM уменьшит размер стопки до указанного -XMS. Если потребность в памяти будет удовлетворена после полной сборки мусора, она будет динамически скорректирована, и ресурс будет использован.
  • Новое поколение должно быть установлено как можно больше, так что объекты могут выжить в новом поколении в течение более длительного периода времени. Каждый второстепенный GC должен собирать как можно большего количества мусора, чтобы предотвратить или задержать шанс объектов, входящих в старое поколение. , чтобы уменьшить частоту полного GC в приложении.,
  • Если сборщик CMS используется в старом поколении, новое поколение не должно быть слишком большим, потому что скорость параллельного сбора CMS также очень высока, а одновременная маркировка и параллельная очистка, которые занимают много времени в процесс сбора может выполняться одновременно с пользовательским потоком.
  • Размер области метода установлен.До 1.6 нужно учитывать константы и статические переменные, которые динамически добавляются при работе системы.В 1.7 пока можно почти подгрузить информацию о классе, динамически подгружаемую при запуске и позже .

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

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

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

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

  • Мягкие ссылки и слабые ссылки можно использовать в соответствующих сценариях (таких как реализация кэширования), например, использование мягких ссылок для выделения экземпляров в ObjectA: SoftReference objectA=new SoftReference(); до того, как произойдет переполнение памяти, объект A будет включен в область действия переработка на двоих Если в этой коллекции недостаточно памяти, будет выдано исключение нехватки памяти. Избегайте бесконечного цикла.После создания бесконечного цикла в теле цикла может многократно генерироваться большое количество экземпляров, что приводит к быстрому заполнению пространства памяти.

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

(4) Запись об устранении неполадок JVM

Устранение неполадок службы JVMblog.CSDN.net/JA CIN1/Ariti…

Незабываемое устранение неисправностей частого полного процесса GCМассовый 81.ITeye.com/blog/151334…

Частое расследование онлайн FullGCblog.CSDN.net/Wilson держит 3…

[JVM] Устранение неполадок онлайн-приложенийблог woo woo woo.cn на.com/D house/afraid/78…

Процесс устранения неполадок для FullGC в JVMIA люди используют .iteye.com / blog / 183026 ...

Устранение неполадок в случаях высокой загрузки ЦП, вызванных переполнением памяти JVMblog.CSDN.net/Не Линьцинь 520…

Случай устранения неполадок с утечкой памяти в Javablog.CSDN.net/AA — это GIS6U/AR…

(5) Общая ссылка на параметры JVM:

параметр инструкция пример
-Xms Начальный размер кучи, 1/64 физической памяти по умолчанию -Xms512M
-Xmx Максимальный размер кучи, 1/4 физической памяти по умолчанию -Xms2G
-Xmn Размер памяти нового поколения, официальная рекомендация — 3/8 всей кучи. -Xmn512M
-Xss Размер стека потоков, по умолчанию 1M для jdk1.5 и более поздних версий, по умолчанию 256k до -Xss512k
-XX:NewRatio=n Установите соотношение молодого поколения к старому поколению. Например, это 3, что означает, что соотношение молодого поколения к старому составляет 1:3, а молодое поколение составляет 1/4 от суммы молодого поколения и старого поколения. -XX:NewRatio=3
-XX:SurvivorRatio=n Отношение площади Эдема к двум областям Выживших в молодом поколении. Обратите внимание, что в области выживших их два. Например: 8 означает Эдем: Выживший = 8:1:1, область Выживших занимает 1/8 часть всего молодого поколения. -XX:SurvivorRatio=8
-XX:PermSize=n Начальное значение постоянной генерации, по умолчанию 1/64 физической памяти -XX:PermSize=128M
-XX:MaxPermSize=n Максимальное значение постоянной генерации, по умолчанию 1/4 физической памяти -XX:MaxPermSize=256M
-verbose:class Вывести информацию о загрузке класса в консоль
-verbose:gc распечатать журнал сборки мусора в консоли
-XX:+PrintGC Распечатайте журнал GC, содержание простое
-XX:+PrintGCDetails Распечатать журнал GC с подробным содержимым
-XX:+PrintGCDateStamps Добавить временную метку в журнал GC
-Xloggc:filename Укажите путь к журналу gc -Xloggc:/data/jvm/gc.log
-XX:+UseSerialGC Серийный коллекционный набор для молодого поколения Serial
-XX:+UseParallelGC Молодое поколение создало параллельный коллектор Parallel Scavenge
-XX:ParallelGCThreads=n Устанавливает количество ЦП, используемых при сборе Parallel Scavenge. Количество параллельных потоков сбора. -XX:ParallelGCThreads=4
-XX:MaxGCPauseMillis=n Установите максимальное время (миллисекунды) для перезапуска Parallel Scavenge -XX:MaxGCPauseMillis=100
-XX:GCTimeRatio=n Установите время сборки мусора Parallel Scavenge в процентах от времени выполнения программы. Формула 1/(1+n) -XX:GCTimeRatio=19
-XX:+UseParallelOldGC Установите старое поколение в качестве параллельного коллектора. Параллельный старый коллектор.
-XX:+UseConcMarkSweepGC Настройка CMS с параллельным сборщиком старого поколения
-XX:+CMSIncrementalMode Установите сборщик CMS в добавочный режим, который подходит для ситуаций с одним ЦП.

Пять, загрузка класса


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

1. Шаг загрузки класса

От загрузки в память до выгрузки из памяти после использования класс должен пройти через процессы загрузки, подключения, инициализации, использования и выгрузки.Соединение можно подразделить на проверку, подготовку и синтаксический анализ.
这里写图片描述

(1) нагрузка

На этапе загрузки виртуальная машина в основном выполняет три задачи:
① Получите двоичный поток, определяющий класс, через полное имя класса (например, com.danny.framework.t);
② Преобразовать статическую структуру хранения, представленную этим потоком байтов, в структуру хранения времени выполнения области метода;
③ Создайте объект java.lang.Class, представляющий этот класс в памяти как внешний интерфейс этого класса в области методов доступа к программе.

(2) Проверка

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

  • Проверка формата файла: в основном проверьте формат двоичного потока байтов в файле класса, например, начинается ли магическое число с 0xCAFEBABY, правильный ли номер версии и т. д.

  • Проверка метаданных: в основном выполнять семантический анализ информации, описанной байт-кодом, чтобы убедиться, что она соответствует спецификации языка Java, например, проверить, есть ли у этого класса родительский класс (кроме java.lang.Object), и если класс не является абстрактный класс, вне зависимости от того, реализованы ли методы, не реализованные в родительских классах или интерфейсах и т. д.

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

  • Проверка символьной ссылки: проверьте соответствие информации, отличной от самого класса, например, можно ли найти соответствующий класс по полному имени класса и можно ли найти поле/метод, соответствующее имени поля/имени метода. найдены в классе. Если проверка ссылки на символ не пройдена, будут выброшены исключения, такие как "java.lang.NoSuchFieldError", "java.lang.NoSuchMethodError".

(3) Подготовка

[] Является формальной переменной класса выделяет память и устанавливает начальное значение переменной класса [], переменные, используемые в процессе выделенной области памяти. Обратите внимание, что выделение памяти представляет собой объект «переменные класса» вместо переменных экземпляра и назначается «начальному значению», начальное значение, как правило, числового типа равно 0, начальное значение типа char '\ u0000' (постоянный пул Nul представляет собой строку), начальное значение логического типа — false, тип начального значения ссылки — null. Но с ключевым словом final, таким как public static final int value = 123, фаза подготовки 123 инициализирует значение значения;

(4) Анализ

Разбор — это процесс замены [символических ссылок] в пуле констант на [прямые ссылки].

Символическая ссылка — это набор символов для описания цели, на которую ссылаются.Символическая ссылка не имеет ничего общего со структурой памяти, реализованной виртуальной машиной, и цель, на которую ссылаются, не обязательно загружается в память. Например, com.danny.framework.Logger упоминается в классе com.danny.framework.LoggerFactory, но адрес памяти класса Logger неизвестен во время компиляции, поэтому com.danny.framework.Logger можно использовать только первым. (предполагая, что this на самом деле представлено константой, похожей на CONSTANT_Class_info) для представления адреса класса Logger, который является символической ссылкой.

Прямая ссылка может быть указателем непосредственно на цель, относительным смещением или дескриптором, который может косвенно указывать на цель. Прямая ссылка связана со структурой памяти, реализованной виртуальной машиной.Если есть прямая ссылка, указанная цель должна существовать в памяти.

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

(5) Инициализация

На этапе подготовки, был присвоен начальные значения переменных класса в фазе инициализации, чтобы инициализировать переменные класса и другие ресурсы в соответствии с программистом через программы с учетом субъективной программы, она также может быть понято с другой точки зрения: начальная стадия реализации методы процесс класса строитель (), и что () в конце концов , что это такое? Это мое понимание, Java байт - код в генерации, если класс имеет назначение кодового блока статической или статической переменной, будет конструктор класса () и конструктор экземпляра () метод синтаксического дерева (Следует принять во внимание на этапе компиляции автоматически скрыт класс добавляет два метода: конструктор класса - ( + ) методы и конструкторов экземпляров - ( + ) метод, можно рассматривать команду javap), () она используется в основном для класса конструкта, такие как переменные класса инициализации (статические переменные), выполняют статический блок кода (Statis {}) и тому подобное, способ выполняется только один раз; () метод в основном используется для построения примера, в ходе строительства , например, сначала выполнить (), то объект Все переменные - члены будет установлено значение по умолчанию (значение по умолчанию для каждого типа данных и загрузчик классов , как описано в стадии подготовки), а затем будет выполнять экземпляр конструктора (сначала выполняет родительский класс конструктора, а затем выполнить нестатический блок кода последний конструктор выполняется).

Давайте посмотрим на код, чтобы понять:

public class Parent {
    static {
        System.out.println("Parent-静态代码块执行");
    }

    public Parent() {
        System.out.println("Parent-构造方法执行");
    }

    {
        System.out.println("Parent-非静态代码块执行");
    }
}

public class Child extends Parent{
    private static int staticValue = 123;
    private int noStaticValue=456;

    static {
        System.out.println("Child-静态代码块执行");
    }

    public Child() {
        System.out.println("Child-构造方法执行");
    }

    {
        System.out.println("Child-非静态代码块执行");
    }

    public static void main(String[] args) {
        Child child = new Child();
    }
}

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

Parent-静态代码块执行
Child-静态代码块执行
Parent-非静态代码块执行
Parent-构造方法执行
Child-非静态代码块执行
Child-构造方法执行

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

2. Загрузчик классов

(1) Роль загрузчика классов

  • Загрузка класса: первый шаг фазы загрузки загрузки класса выполняется загрузчиком классов. Основная задача загрузчика классов - «получить двоичный поток байтов, описывающий этот класс, через полное имя класса», Здесь, двоичный поток, загруженный загрузчиком классов, не обязательно должен быть получен из файла класса, но также может быть прочитан из других форматов, таких как zip-файлы, прочитан из сети или базы данных, динамически сгенерирован во время выполнения, сгенерирован другими файлами (для например, jsp генерирует файлы классов классов) и так далее. С точки зрения программиста, загрузчик классов динамически загружает файл класса в виртуальную машину и создает экземпляр java.lang.Class, каждый экземпляр представляет класс java, и информация о классе может быть получена в соответствии с экземпляром. , а также Объект этого класса может быть сгенерирован методом newInstance().

  • Определяет уникальность класса: загрузчик класса имеет поворотную роль в дополнение к загрузке нагрузки, и для каждого класса его необходимо использовать, загружая его загрузчик и сам этот класс, чтобы установить этот класс в виртуальной машине Java. , Уникальность. То есть два одинаковых класса "равны" только в том случае, если загружен один и тот же загрузчик, "equal" - это метод equals() объекта класса, представляющий объект класса, метод isassileFrom(), isinstance() Возвращаемый результат метода также включает результат связи ключевого слова INSTANCEOF с принадлежностью объекта.

(2) Классификация погрузчиков класса

С точки зрения разработчика загрузчики классов делятся на следующие типы: Bootstrap ClassLoader, Extension ClassLoader, Application ClassLoader и Custom ClassLoader (User ClassLoader), где загрузчик классов запуска является частью JVM, остальные загрузчики классов реализованы в java и в конечном итоге наследуется от java.lang.ClassLoader.

① Bootstrap ClassLoader скомпилирован из C/C++, и исходный код не виден, поэтому определение Bootstrap ClassLoader, показанное в исходном коде java.lang.ClassLoader, является родным "частным родным классом findBootstrapClass(String name);" . Загрузчик загрузочного класса в основном отвечает за загрузку каталога JAVA_HOME\lib или некоторых классов в каталоге, указанном параметром -Xbootclasspath. Конкретные загруженные классы можно просмотреть с помощью "System.getProperty("sun.boot.class.path") ".

② Расширение ClassLoader реализовано sun.misc.Launcher.ExtClassLoader и отвечает за загрузку всех библиотек классов в каталог JAVA_HOME\lib\ext или путь, указанный системной переменной java.ext.dirs. .getProperty("java. ext.dirs")", чтобы увидеть, какие классы загружены.

③ Загрузчик классов приложения (Application ClassLoader) реализован с помощью sun.misc.Launcher.AppClassLoader, который отвечает за загрузку классов в пользовательский путь к классам (который мы обычно указываем).Если в программе нет пользовательского загрузчика классов, класс приложения загружается. Загрузчик классов по умолчанию является загрузчиком классов программы по умолчанию.

④ Пользовательский загрузчик классов (User ClassLoader), загрузчик классов, предоставляемый JVM, может загружать только классы (jar и класс) из указанного каталога. Если мы хотим получить файлы классов из других мест или даже из сети, нам нужно настроить загрузчик классов Для достижения этого пользовательский загрузчик классов в основном реализуется путем наследования ClassLoader или его подклассов, но независимо от того, наследуется ли ClassLoader или его подклассы, родительский загрузчик конечного пользовательского загрузчика классов является загрузкой класса приложения. Потому что независимо от того, какой загрузчик родительского класса созданный объект должен, наконец, вызвать java.lang.ClassLoader.getSystemClassLoader() в качестве родительского загрузчика, а возвращаемое значение метода getSystemClassLoader() — sun.misc.Launcher.AppClassLoader, который является загрузчиком класса приложения.

(3) Модель делегирования ClassLoader с родителями

Давайте взглянем на метод loadclass() базовой логики в java.lang.classloader:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 检查该类是否已经加载过
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {//如果父加载器不为空,就用父加载器加载类
                        c = parent.loadClass(name, false);
                    } else {//如果父加载器为空,就用启动类加载器加载类
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {//如果上面用父加载器还没加载到类,就自己尝试加载
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

Основное значение этого кода заключается в том, что когда загрузчик классов загружает класс, если есть родительский загрузчик, попробуйте сначала загрузить родительский загрузчик.Если у родительского загрузчика все еще есть родительский загрузчик, он будет продолжать выбрасывать и загружать класс все время Задача запуска загрузчика класса передается загрузчику запускаемого класса, а затем загрузчик запускающего класса выдает исключение ClassNotFoundException, если класс не может быть загружен, а затем бросает задачу загрузки класса вниз, как показано на следующем рисунке:
这里写图片描述

В процессе загрузки класса на приведенном выше рисунке вводится более важная концепция — модель родительского делегирования, как показано на следующем рисунке.Родительская модель делегирования требует, чтобы в дополнение к загрузчику класса запуска верхнего уровня другие загрузчики классов be Существует загрузчик родительского класса, но это отношение родитель-потомок является не отношением наследования, а композиционным отношением, как показано в приведенном выше коде.
这里写图片描述

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

Итак, каковы преимущества модели родительского делегирования? Самым большим преимуществом является то, что он дает классам в Java тот же «приоритет», что и загрузчики классов. Как упоминалось ранее, для каждого класса уникальность класса в виртуальной машине Java должна быть установлена ​​загружающим его загрузчиком и самим классом, таким как класс java.lang.Object (хранящийся в JAVA_HOME\lib\rt .jar ), если пользователь пишет класс java.lang.Object и загружает его с помощью специального загрузчика классов, то есть ли в программе два класса? Поэтому модель родительского делегирования очень важна для обеспечения стабильной работы Java.

Уведомление:Выше приведено резюме редактора после изучения "Глубокое понимание виртуальной машины Java". Содержание частично взято из книги, а также ссылается на бесчисленные материалы в Интернете. Я хотел бы поблагодарить великих богов за самоотверженный обмен. 【Перепечатка, пожалуйста, укажите источник - Ху Юян"Заметки об исследовании JVM"