JVM последовательное чтение журналов сборщика мусора

JVM
JVM последовательное чтение журналов сборщика мусора

С развитием экосистемы Java сегодня ценность JVM (виртуальная машина Java), возможно, превзошла сам язык Java.

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

В этой статье подробно рассказывается о журнале сборщика мусора с различных аспектов.Эта статья основана на OpenJDK1.8 и виртуальной машине HotSpot.

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

$ java -version
openjdk version "1.8.0_40"
OpenJDK Runtime Environment (build 1.8.0_40-b25)
OpenJDK 64-Bit Server VM (build 25.40-b25, mixed mode)

Вышеупомянутое использоватьjava -versionПечатная информация содержит много информации, например, выше показано, что JDK — openjdk1.8, текущая JVM — Server VM, и используется смешанный режим выполнения.

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

Вы можете использовать следующую команду для переключения JVM.

$ java -client -version
$ java -server -version

Но следует отметить,Для 64-битного JDK только виртуальная машина сервера, Java отказалась от настольных приложений, и в будущем вы можете сосредоточиться на Server VM.

Java — это сложный язык, который можно интерпретировать и выполнять, компилировать и выполнять, или оба способа могут сосуществовать.По умолчанию оба способа сосуществуют (mixed mode), именно JVM решает, какой код компилировать и выполнять.

Эти три режима выполнения также можно переключать:

$ java -Xint -version # 完全解释执行
$ java -Xcomp -version # 完全编译执行
$ java -Xmixed -version # 混合执行

Механизмы GC Client VM и Server VM различны.В этой статье мы сосредоточимся только на GC Server VM.

Модель памяти JVM

Следующие два рисунка очень важны.Поскольку упоминается JVM, будут использоваться следующие два рисунка (виртуальная машина HotSpot).

Модель памяти JVM выглядит следующим образом:


После Java 8 область методов реализуется с использованием метапространства и не занимает место в куче, а реализуется с использованием локальной памяти.

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


Регионы Эдем, откуда и куда называются молодыми регионами.Соотношение этих трех регионов по умолчанию составляет 8: 1: 1, а соотношение всего региона молодого поколения к региону старого поколения составляет 1: 2.

Базовая классификация ГХ

Вообще говоря, GC можно разделить на две категории: GC для некоторых областей и GC для всей кучи (Full GC).В общем, GC происходит только в некоторых областях.Если GC всей кучи происходит часто, может быть память утечки и тд.

GC для некоторых регионов может происходить в молодом поколении (Minor GC) или в старом поколении (Major GC).

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


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

параметры ГХ

По умолчанию программа Java не будет печатать журнал GC при запуске, вам необходимо добавить следующие параметры JVM:

-verbose:gc # 控制台打印 gc 日志
-XX:+PrintGCDetails # 打印 gc 过程的细节
-XX:+PrintGCDateStamps # 打印 gc 的发生的时间戳

Если вы хотите сохранить в виде файла, вам необходимо добавить следующие параметры.Следует отметить, что если вы сохраняете в виде файла, консоль больше не будет печатать журнал gc:

-Xloggc:gc.log # 将 gc 日志保存成文件,默认在项目的根目录,可以根据项目的情况进行调整

Есть и другие вспомогательные параметры:

-XX:+PrintGCApplicationStoppedTime # 打印 stop world 的时间
-XX:+PrintGCApplicationConcurrentTime # 打印程序未中断运行的时间
-XX:+PrintHeapAtGC # 打印 GC 前后的堆栈信息
-XX:+PrintTenuringDistribution # 打印每次 minor GC 后新的存活周期的阈值

Понимание журналов GC

Во-первых, давайте взглянем на базовый журнал.Установлены следующие параметры:

-Xms8m
-Xmx8m
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintHeapAtGC
-XX:+PrintTenuringDistribution

Затем выполните следующий код:

public class NoGC {
    public static void main(String[] args) {
        System.out.println("No gc");
    }
}

В результате журнал выглядит следующим образом:

Heap
 PSYoungGen      total 2048K, used 1400K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)
  eden space 1536K, 91% used [0x00000000ffd80000,0x00000000ffede3d0,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 5632K, used 0K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)
  object space 5632K, 0% used [0x00000000ff800000,0x00000000ff800000,0x00000000ffd80000)
 Metaspace       used 3018K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 323K, capacity 388K, committed 512K, reserved 1048576K

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

надHeapУказывает, что это куча JVM, которая в основном делится на три части, PSYoungGen (новое поколение), ParOldGen (старое поколение) и Metaspace (пространство метаданных, то есть область методов).

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

PSYoungGen представляет молодое поколение и используетParallel ScavengeСборщик для GC, ParOldGen представляет старость, и использованиеParallel oldКоллектор приходит к GC, и ВМ сервера использует комбинацию этой пары коллекторов.

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

Старое поколение не подразделяется на другие регионы, и те объекты, которые не устранены в молодом поколении, войдут в старое поколение.

Происходит частичный GC

Выполните следующий код:

public class NormalGC {
    public static void main(String[] args) throws InterruptedException {

        Object o  = new Object();
        for (int i = 0; ;i++) {
            Thread.sleep(1000);
            byte[] b = new byte[1024 * 1024];
            b = null;
        }
    }
}

В результате журналы сборщика мусора выглядят следующим образом:

{Heap before GC invocations=1 (full 0):
 PSYoungGen      total 2048K, used 1536K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)
  eden space 1536K, 100% used [0x00000000ffd80000,0x00000000fff00000,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 5632K, used 0K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)
  object space 5632K, 0% used [0x00000000ff800000,0x00000000ff800000,0x00000000ffd80000)
 Metaspace       used 3136K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 333K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) 
Desired survivor size 524288 bytes, new threshold 7 (max 15)
[PSYoungGen: 1536K->496K(2048K)] 1536K->560K(7680K), 0.0024873 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap after GC invocations=1 (full 0):
 PSYoungGen      total 2048K, used 496K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)
  eden space 1536K, 0% used [0x00000000ffd80000,0x00000000ffd80000,0x00000000fff00000)
  from space 512K, 96% used [0x00000000fff00000,0x00000000fff7c010,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 5632K, used 64K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)
  object space 5632K, 1% used [0x00000000ff800000,0x00000000ff810000,0x00000000ffd80000)
 Metaspace       used 3136K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 333K, capacity 388K, committed 512K, reserved 1048576K
}
Total time for which application threads were stopped: 0.0029243 seconds, Stopping threads took: 0.0000297 seconds
Application time: 1.0001808 seconds

Давайте посмотрим на структуру этого журнала в целом.

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

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

Предпоследняя строкаPrintGCApplicationStoppedTimeИнформация, напечатанная параметром, указывает время паузы из-за GC. Последняя строкаPrintGCApplicationConcurrentTimeИнформация, напечатанная параметром, указывает время выполнения программы.

Давайте посмотрим на детали утилизации GC, Из приведенного выше журнала мы видим, что область eden нового поколения заполнена, а затем появляется следующий журнал:

[GC (Allocation Failure) 
Desired survivor size 524288 bytes, new threshold 7 (max 15)
[PSYoungGen: 1536K->496K(2048K)] 1536K->560K(7680K), 0.0024873 secs] 
[Times: user=0.01 sys=0.00, real=0.01 secs] 

Этот журнал представляет собой второстепенный GC, выполненный в молодом поколении.

В первой строке указано, что причиной запуска этого GC является сбой распределения, а во второй строке — потому что мы добавили-XX:+PrintTenuringDistributionЭтот параметр, он будет печатать возраст объекта нового поколения, и порог возраста объекта, входящего в старое поколение, по умолчанию здесь 7, максимум 15, который можно настроить параметрами.

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

  • 1536K->496K(2048K) означает:Площадь, используемая в этой зоне до утилизации → Площадь, используемая в этой зоне после утилизации (общая площадь в этой зоне), здесь относится к кайнозойской области
  • 1536K->560K(7680K) означает:Используемое пространство в куче до высвобождения → используемое пространство в куче после высвобождения (общее пространство в куче)
  • 0,0024873 с: указывает время выполнения GC в секундах.
  • Times: user=0,01 sys=0,00, real=0,01 sec: Эта часть также подсчитывает время GC, но с точки зрения операционной системы точки выражаются как время пользовательского режима, время режима ядра и время часов.

Происходит полный сборщик мусора

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

public class FullGC {
    public static void main(String[] args) {
        int size = 1024 * 1024;
        byte[] b = new byte[size];

        b = null;

        System.gc();
    }
}

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

[Full GC (System.gc()) 
[PSYoungGen: 480K->0K(2048K)] 
[ParOldGen: 1104K->473K(5632K)] 
1584K->473K(7680K),
[Metaspace: 3107K->3107K(1056768K)], 
0.0055681 secs] 
[Times: user=0.02 sys=0.00, real=0.01 secs] 

Первая строка указывает, что на этот раз произошла полная сборка мусора, которая была инициирована из-за вызова System.gc().

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

Четвертая строка также указывает на восстановление всей памяти кучи.

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

Если память утекает или переполняется

OutOfMemoryError — распространенная ошибка, обычно перед выдачей этого исключения происходит полный сборщик мусора.

public class Leak2GC {
    public static void main(String[] args) {
         Vector strings = new Vector();

        for (int i = 0;  ;i++) {
            String str = new String("Hello gc" + i);
            strings.add(str);
            str = null;
        }
    }
}

После выполнения приведенного выше кода появится следующий журнал:

[Full GC (Allocation Failure) 
[PSYoungGen: 943K->943K(2048K)] 
[ParOldGen: 5556K->5539K(5632K)] 
6499K->6483K(7680K), 
[Metaspace: 3135K->3135K(1056768K)],
0.0260635 secs]
[Times: user=0.14 sys=0.00, real=0.03 secs] 

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

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.Vector.grow(Vector.java:266)
	at java.util.Vector.ensureCapacityHelper(Vector.java:246)
	at java.util.Vector.add(Vector.java:782)
	at cn.rayjun.java.java8.gc.Leak2GC.main(Leak2GC.java:12)

Статистический анализ информации ГХ

Благодаря вышеприведенному содержимому вы сможете понять информацию журнала GC.Во многих случаях мы не можем судить о проблеме по некоторым журналам.

Поэтому необходимо вести статистику общей ситуации с ГК, как правило, с помощью других статистических инструментов.

Самый прямой способ — использовать встроенный инструмент JDK, jstat, который используется следующим образом, а параметром является pid.

$ jstat -gc 24991
S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
512.0  512.0   0.0    0.0    1536.0   1024.0    5632.0     4755.0   4864.0 3477.4 512.0  378.3      54    0.082   1      0.018    0.099

Значение каждого элемента данных следующее:

  • S0C: размер области from
  • S1C: размер области
  • S1U: Размер, используемый областью from
  • S1U: чтобы перейти к использованию размера
  • EC: размер райской области
  • ЕС: Эдем использовать размер
  • OC: размер старого поколения
  • OU: размер, используемый старым поколением
  • MC: размер области метода
  • MU: Размер используемой области метода
  • CCSC: Пространство класса сжатия
  • CCUS: размер, используемый сжатым пространством класса.
  • YGC: Количество сборщиков мусора молодого поколения
  • YGCT: время потребления мусора молодым поколением
  • FGC: количество сборок мусора старого поколения
  • FGCT: сбор мусора для пожилых людей требует времени
  • GCT: общее время сборки мусора

Вы также можете использовать инструменты визуального анализа для анализа журналов компьютерной графики, такие какgceasy.io/.

Если используются другие коллекторы

В приведенном выше журнале GC использует комбинацию сборщиков по умолчанию, мы также можем использовать другие сборщики, например использованиеUseConcMarkSweepGCпараметр для включения сборщика CMS.

После использования этого сборщика лог такой:

[GC (Allocation Failure) [ParNew: 1384K->256K(2432K), 0.0022504 secs] 1384K->491K(7936K), 0.0022693 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [CMS: 235K->472K(5504K), 0.0016647 secs] 1582K->472K(7936K), [Metaspace: 3009K->3009K(1056768K)], 0.0016851 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

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

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

Текст / Райджун