Estructura de memoria JVM y ajuste de GC

1. Introducción a JVM

1.1 ¿Qué es JVM?

Java Virtual Machine (Java Virtual Machine)
Escribir una vez Ejecutar en cualquier lugar
inserte la descripción de la imagen aquí

1.2 JDK JRE JVM

Sitio web oficial de Java: https://docs.oracle.com/javase/8/
Referencia -> Guías para desarrolladores -> Vaya a:
https://docs.oracle.com/javase/8/docs/index.html

JDK 8 es un superconjunto de JRE 8 y contiene todo lo que hay en JRE 8, además de
herramientas como los compiladores y depuradores necesarios para desarrollar applets y aplicaciones. JRE 8 proporciona las bibliotecas, la máquina virtual Java (JVM) y otros componentes para ejecutar applets y aplicaciones escritas en el lenguaje de programación Java. Tenga en cuenta que el JRE incluye componentes no requeridos por la especificación Java SE, incluidos componentes Java estándar y no estándar.

inserte la descripción de la imagen aquí

Dos mecanismos operativos JVM

2.1 Código fuente para archivos de clase

2.1.1 Demostración del código fuente

class Person{
    
    
	private String name="刘同学";
	private int age;
	private final double salary=100;
	private static String address;
	private final static String hobby="Programming";
	private static Object obj=new Object();
	public void say(){
    
    
		System.out.println("person say...");
	}
	public static int calc(int op1,int op2){
    
    
	op1=3;
	int result=op1+op2;
	Object obj=new Object();
		return result;
	}
	public static void main(String[] args){
    
    
		calc(1,2);
	}
}

Compilar: javac -g:vars Persona.java —> Persona.clase

2.1.2 Precompilación

Person.java -> Analizador léxico -> Flujo de tokens -> Analizador de sintaxis -> Árbol de sintaxis/Árbol de sintaxis abstracta
-> Analizador semántico -> Árbol de sintaxis abstracta anotado -> Generador de bytecode -> Archivo Person.class

2.1.3 Archivos de clase (Class files)

2.1.3.1 Hexadecimales

café nena 0000 0034 003f 0a00 0a00 2b08 002c 0900 0d00 2d06 4059 0000
0000 0000 0900 0d00 2e09
002f 0030 0800 310a 0032 0033 0700
340a 000d 0035 0900 0d00 3607 0037 0100 046e 616d 6501 0012 4c6a 6176 612f 6c61 6e67 2f53 7472
696e
673b 0100 0361
6765 0100 0149 0100 0673 616c 6172
7901 0001 4401 000d 436f 6e73 7461 6e74

2.1.3.2 La estructura ClassFile

Sitio web oficial: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

Archivo de clase { u4 magic; u2 versión_menor; u2 versión_principal; u2 conteo_agrupación_constante; cp_info constante_pool[constant_pool_count-1]; u2 banderas_de_acceso; u2 esta_clase; u2 super_clase; u2 interfaces_cuenta; u2 interfaces[interfaces_count]; u2 campos_cuenta; field_info campos[fields_count]; u2 métodos_cuenta; method_info métodos[methods_count]; u2 atributos_cuenta; atributo_info atributos[atributos_cuenta]; }
















2.1.3.3 Análisis simple

u4: cafebabe

magia: el elemento mágico proporciona el número mágico que identifica el
formato de archivo de clase

u2+u2:0000+0034, 34 es igual a 52 en decimal, lo que significa JDK8

versión_menor
versión_mayor

u2:003f=63 (sistema decimal)

constant_pool_count:
el valor del elemento constant_pool_count es igual al número de entradas
en la tabla constant_pool más uno.

Indica que el número en el grupo constante es 62
cp_info constant_pool[constant_pool_count-1]

El constant_pool es una tabla de estructuras que representan varias constantes de cadena, nombres de clases e interfaces, nombres de campos y otras constantes a las que se hace referencia dentro de la estructura ClassFile y sus subestructuras. El formato de cada entrada de la tabla constant_pool se indica mediante su primer byte de "etiqueta".
La tabla constant_pool está indexada de 1 a constant_pool_count - 1.

El grupo constante almacena principalmente dos aspectos: referencias literales y simbólicas

Cantidad literal: cadena de texto, modificación final, etc.
Símbolo de referencia: nombre completo de clase e interfaz, nombre de campo y descriptor, nombre de método y descriptor

2.1.3.4 verificación javap

Comandos que vienen con el JDK

javap-h

Puede verificar la corrección de las primeras piezas de la estructura del archivo de clase anterior

javap -v -p Person.class 进行反编译,查看字节码信息和指令等信息
是否有一种感觉?
JVM相对class文件来说可以理解为是操作系统;class文件相对JVM来说可以理解为是汇编语言或者机器语言。

2.1.3.5 Continous analysis

上面分析到常量池中常量的数量是62,接下来我们来具体分析一下这62个常量cp_info constant_pool[constant_pool_count-1] 也就是这块包括的信息cp_info其实就是一个表格的形式
All constant_pool table entries have the following general format:

cp_info{
u1tag;
u1info[];
}

官网 :https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4

inserte la descripción de la imagen aquí

(1)往下数一个u1,即0a->10:代表的是CONSTANT_Methodref,表示这是一个方法引用

CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}

Cuenta atrás u2 y u2
u2, es decir, 00 0a->10: representa class_index, indicando el índice
u2 de la clase a la que pertenece el método en el pool de constantes, es decir, 00 2b->43: representa name_and_type_index, indicando el índice de nombres y tipos del método

#1 = Métodoref #10,#43

(2) Cuenta regresiva u1 , es decir, 08->8: significa CONSTANT_String, lo que significa el tipo de cadena

CONSTANT_String_info { u1 etiqueta; u2 índice_cadena; }


Cuenta regresiva u2
u2, es decir, 00 2c->44: representa string_index

#1 = Método ref #10,#43
#2 = Cadena #44

(3) Cuenta regresiva u1 , es decir, 09->9: significa CONSTANT_Fieldref, lo que significa el tipo de campo

CONSTANT_Fieldref_info { u1 etiqueta; u2 índice_clase; u2 nombre_y_tipo_índice; }



Cuenta atrás u2 y u2
u2, es decir, 00 0d->13: representa class_index
u2, es decir, 00 2d->45: representa name_and_type_index

#1 = Methodref #10.#43
#2 = Cadena #44
#3 = Fieldref #13.#45

2.2 Archivo de clase a máquina virtual (mecanismo de carga de clases)

El llamado mecanismo de carga de clases es

La máquina virtual carga el archivo Class en la memoria
y verifica los datos, convierte, analiza e inicializa
para formar un tipo Java que la máquina virtual puede usar directamente, a saber, java.lang.Class

inserte la descripción de la imagen aquí

2.2.1 Carga

Buscar e importar archivos de clase
(1) Obtener el flujo de bytes binario que define esta clase a través del nombre completo de una clase
(2) Convertir la estructura de almacenamiento estática representada por este flujo de bytes en la estructura de datos de tiempo de ejecución del área de método
(3) Generar un objeto java.lang.Class que representa esta clase en el montón de Java como una entrada de acceso a los datos en el área de método

El objeto Clase encapsula la estructura de datos de la clase en el área de métodos y proporciona a los programadores de Java una interfaz para acceder a la estructura de datos en el área de métodos. Genere un objeto java.lang.Class que represente esta clase en
el montón de Java como una entrada de acceso a los datos en el área de método

inserte la descripción de la imagen aquí

2.2.2 Enlace

2.2.2.1 Verificar

Asegurar la corrección de las clases cargadas

Validación de formato de archivo Validación
de metadatos Validación
de código de bytes
Validación de referencia de símbolo

2.2.2.2 Preparar

Asigne memoria para las variables estáticas de la clase e inicialícelas a los valores predeterminados

inserte la descripción de la imagen aquí

public class Demo1 {
    
    
	private static int i;
	public static void main(String[] args) {
    
    
		// 正常打印出0,因为静态变量i在准备阶段会有默认值0
		System.out.println(i);
	}
}
public class Demo2 {
    
    
	public static void main(String[] args) {
    
    
		// 编译通不过,因为局部变量没有赋值不能被使用
		int i;
		System.out.println(i);
	}
}

2.2.2.3 Resolver

Convertir referencias simbólicas en clases a referencias directas

Una referencia de símbolo es un conjunto de símbolos para describir el destino, que puede ser cualquier literal.
Una referencia directa es un puntero directamente al objetivo, un desplazamiento relativo o un identificador que localiza indirectamente el objetivo.

La fase de análisis es el proceso en el que la máquina virtual reemplaza las referencias simbólicas en el grupo constante con referencias directas.
La acción de análisis se realiza principalmente en 7 tipos de referencias de símbolos de clase o interfaz, campo, método de clase, método de interfaz, tipo de método, identificador de método y calificador de llamada.

2.2.3 Inicializar

Realizar operaciones de inicialización en variables estáticas y bloques de código estáticos de la clase
inserte la descripción de la imagen aquí

2.2.4 Cargador de clases Cargador de clases

En la etapa de carga (carga), el paso (1): obtener el flujo de bytes binarios definido por el nombre completo de la clase, que debe completarse con la ayuda de un cargador de clases. Como sugiere el nombre, se utiliza para cargar el archivo de clase.

2.2.4.1 Clasificación

inserte la descripción de la imagen aquí

2.2.4.2 Diagrama

inserte la descripción de la imagen aquí

public class Demo3 {
    
    
	public static void main(String[] args) {
    
    
	// App ClassLoader
	System.out.println(new Worker().getClass().getClassLoader());
	// Ext ClassLoader
	System.out.println(new
	Worker().getClass().getClassLoader().getParent());
	// Bootstrap ClassLoader
	System.out.println(new
	Worker().getClass().getClassLoader().getParent().getParent());
	System.out.println(new String().getClass().getClassLoader());
	}
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@3a71f4dd
null
null

2.2.4.3 Principio de carga [delegación principal]

(1) Compruebe si se ha cargado una determinada clase.
De abajo hacia arriba, compruebe capa por capa desde Custom ClassLoader hasta BootStrap ClassLoader. Siempre que se haya cargado un cierto Classloader, se considera que ha cargado esta clase, asegurándose de que La clase solo se carga una vez por todos los ClassLoaders.

(2) El orden de carga
es de arriba hacia abajo, es decir, la capa superior intenta cargar esta clase capa por capa.
inserte la descripción de la imagen aquí

2.2.4.4 Romper la delegación de los padres

(1) gato
inserte la descripción de la imagen aquí

(2) mecanismo SPI
(3) OSGi

2.3 Áreas de datos de tiempo de ejecución

En los pasos (2) y (3) de la fase de carga, se puede encontrar que hay sustantivos como datos de tiempo de ejecución, montón y área de método (2)
Convierta la estructura de almacenamiento estática representada por este flujo de bytes en datos de tiempo de ejecución. estructura del área de métodos
(3) Genere un objeto java.lang.Class que represente esta clase en el montón de Java como una entrada de acceso a los datos en el área de métodos. Para decirlo
sin rodeos, después de que el cargador de clases cargue el archivo de clase , el contenido de la clase (como variables, constantes, métodos, objetos y otros datos debe tener un lugar para ir, es decir, para almacenarse, y la ubicación de almacenamiento debe tener un espacio correspondiente en la JVM)

2.3.1 Descripción general del sitio web oficial

Sitio web oficial: https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

La máquina virtual de Java define varias áreas de datos en tiempo de ejecución que se utilizan durante la ejecución de un programa. Algunas de estas áreas de datos se crean en el inicio de la máquina virtual Java y se destruyen solo cuando se cierra la máquina virtual Java. Otras áreas de datos son por subproceso. Las áreas de datos por hilo se crean cuando se crea un hilo y se destruyen cuando el hilo sale.

2.3.2 Diagrama

Cada conjunto de constantes de tiempo de ejecución se asigna desde el área de métodos de la máquina virtual Java (§2.5.4).

inserte la descripción de la imagen aquí

2.3.3 Comprensión inicial

2.3.3.1 Área de métodos

(1) El área de método es un área de memoria compartida por cada subproceso, que se crea cuando se inicia la máquina virtual

La máquina virtual Java tiene un área de métodos que se comparte entre todos los subprocesos de la máquina virtual Java. El área de métodos se crea al iniciar la máquina virtual.

(2) Aunque la especificación de la máquina virtual de Java describe el área de método como una parte lógica del montón, tiene otro alias llamado Non-Heap (non-heap), cuyo objetivo es distinguirlo del montón de Java.

Aunque el área del método es lógicamente parte del montón,...

(3) Se utiliza para almacenar datos como información de clase, constantes, variables estáticas, código compilado por un compilador en tiempo real, etc. que ha cargado la máquina virtual

Almacena estructuras por clase, como el conjunto de constantes de tiempo de ejecución,
datos de campo y método, y el código para métodos y constructores, incluidos los métodos especiales (§2.9) utilizados en la inicialización de clase e instancia y la
inicialización de interfaz.

(4) Cuando el área del método no puede cumplir con los requisitos de asignación de memoria, se lanzará una excepción OutOfMemoryError

Si la memoria en el área del método no puede estar disponible para satisfacer una solicitud de asignación, la máquina virtual de Java genera un OutOfMemoryError.

inserte la descripción de la imagen aquí
Notable

El área de datos de tiempo de ejecución de JVM es una especificación, y la implementación real
es Metaspace en JDK 8 y Perm Space en JDK6 o 7

2.3.3.2 Montón (montón)

(1) El montón de Java es la pieza de memoria más grande administrada por la máquina virtual de Java. Se crea cuando se inicia la máquina virtual y es compartida por todos los subprocesos.
(2) Las instancias y matrices de objetos Java se asignan en el montón.

La máquina virtual de Java tiene un montón que se comparte entre todos
los subprocesos de la máquina virtual de Java.
El montón es el área de datos en tiempo de ejecución desde la cual se asigna la memoria para todas las instancias de clase y matrices.
El montón se crea en el inicio de la máquina virtual.

En este momento, mire hacia atrás en el tercer paso de la fase de carga y genere un objeto java.lang.Class que represente esta clase en el montón de Java como una entrada de acceso para estos datos en el área de métodos.

En este momento, se puede cambiar la imagen de la carga (1)(2)(3)
inserte la descripción de la imagen aquí

2.3.3.3 Pilas de máquinas virtuales de Java (pila de máquina virtual)

(1) La pila de la máquina virtual es un área donde se ejecuta un subproceso, que guarda el estado de llamada de un método en un subproceso. En otras palabras, el estado de ejecución de un subproceso Java lo guarda una pila de máquina virtual, por lo que la pila de máquina virtual debe ser privada, única y creada con la creación del subproceso.

Cada subproceso de máquina virtual Java tiene una pila de máquina virtual Java privada, creada al mismo tiempo que el subproceso.

(2) Cada método ejecutado por un subproceso es un marco de pila en la pila, es decir, cada método corresponde a un marco de pila.

Cuando se llama a un método, se insertará un marco de pila en la pila; cuando se completa una llamada de método, el marco de pila se extraerá de la pila.

Una pila de Java Virtual Machine almacena marcos (§2.6).

Se crea un nuevo marco cada vez que se invoca un método. Un marco se
destruye cuando se completa la invocación de su método.

Pilas gráficas y marcos de pila

void a(){
    
    
 b();
}
void b(){
    
    
 c();
}
void c(){
    
    
}

inserte la descripción de la imagen aquí

Sitio web oficial del marco de pila
: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6

Marco de pila: cada marco de pila corresponde a un método llamado, que puede entenderse como el espacio de ejecución de un método.
Cada marco de pila incluye una tabla de variables locales (Variables locales), pila de operandos (Pila de operandos), referencia al grupo de constantes de tiempo de ejecución (Referencia al grupo de constantes de tiempo de ejecución), dirección de retorno del método (Dirección de retorno) e información adicional.

Tabla de variables locales: las variables locales definidas en el método y los parámetros del método se almacenan en esta tabla.
Las variables en la tabla de variables locales no se pueden usar directamente. Si es necesario usarlas, se deben cargar en la pila de operandos. a través de instrucciones relevantes y se utilizan como operandos.

Pila de operandos: almacene operandos en forma de push y pop

Vinculación dinámica: cada marco de pila contiene una referencia al método al que pertenece el marco de pila en el conjunto de constantes de tiempo de ejecución. Esta referencia se mantiene para admitir la vinculación dinámica (enlace dinámico) durante la llamada al método.

Dirección de retorno del método: cuando un método comienza a ejecutarse, solo hay dos formas de salir, una es encontrar la instrucción de código de bytes devuelta por el método; la otra es encontrar una excepción, y la excepción no se maneja en el cuerpo del método.

inserte la descripción de la imagen aquí

Combinado con instrucciones de bytecode para comprender el marco de pila
javap -c Person.class > Person.txt

Compiled from "Person.java"
class Person {
    
    
...
public static int calc(int, int);
Code:
0: iconst_3 //将int类型常量3压入[操作数栈]
1: istore_0 //将int类型值存入[局部变量0]
2: iload_0 //从[局部变量0]中装载int类型值入栈
3: iload_1 //从[局部变量1]中装载int类型值入栈
4: iadd //将栈顶元素弹出栈,执行int类型的加法,结果入栈
5: istore_2 //将栈顶int类型值保存到[局部变量2]中
6: iload_2 //从[局部变量2]中装载int类型值入栈
7: ireturn //从方法中返回int类型的数据
...
}

Pensando: ¿Es el valor del índice 0 o 1

En la invocación del método de clase, todos los parámetros se pasan en variables locales consecutivas a partir de la variable local 0. En la invocación del método de instancia, la variable local 0 siempre se usa para pasar una referencia al objeto en el que se invoca el método de instancia (esto en Java lenguaje de programación). Cualquier parámetro se pasa posteriormente en variables locales consecutivas a partir de la variable local 1.

inserte la descripción de la imagen aquí

2.3.3.4 El registro pc (contador de programas)

Todos sabemos que hay varios subprocesos que se ejecutan en un proceso de JVM, y si el contenido del subproceso puede tener derechos de ejecución se basa en la programación de la CPU.
Si el subproceso A se está ejecutando en algún lugar, de repente pierde el derecho de ejecución de la CPU y cambia al subproceso B, entonces cuando el subproceso A obtiene el
derecho de ejecución de la CPU nuevamente, ¿cómo puede continuar ejecutándose? Esta es la necesidad de mantener una variable en el hilo para registrar dónde se ejecuta el hilo.

Si el subproceso está ejecutando un método Java, el contador registra la dirección de la instrucción de código de bytes de la máquina virtual que se está ejecutando;
si se está ejecutando el método nativo, el contador está vacío.

La máquina virtual de Java puede admitir muchos subprocesos de ejecución a la vez (JLS §17). Cada subproceso de la máquina virtual de Java tiene su propio registro de PC (contador de programa). En cualquier momento, cada subproceso de la Máquina Virtual Java está ejecutando el código de un solo método, a saber, el método actual (§2.6) para ese subproceso. Si ese método no es nativo, el registro de pc contiene la dirección de la instrucción de la máquina virtual Java
que se está ejecutando actualmente. Si el método que está ejecutando actualmente el subproceso es nativo, el valor del registro pc de la máquina virtual Java no está definido. El registro de PC de Java Virtual Machine es lo suficientemente amplio como para contener una dirección de retorno o un puntero nativo en la plataforma específica.

2.3.3.5 Pilas de métodos nativos (pila de métodos locales)

Si los métodos ejecutados por el subproceso actual son de tipo nativo, estos métodos se ejecutarán en la pila de métodos nativos.
Entonces, ¿qué pasa si se llama al método nativo cuando se ejecuta el método Java?
inserte la descripción de la imagen aquí

2.3.4 Lanzamiento

2.3.4.1 Apilar puntos en montón

Si hay una variable en el marco de la pila, el tipo es un tipo de referencia, como Object obj=new Object(), entonces es un elemento típico en la pila que apunta al objeto en el montón.
inserte la descripción de la imagen aquí

2.3.4.2 El área del método apunta al montón

Las variables estáticas, las constantes y otros datos se almacenan en el área de métodos. Si este es el caso, los elementos en el área de método típico apuntan a objetos en el montón.

private static Object obj=new Object();

inserte la descripción de la imagen aquí

2.3.4.3 El montón apunta al área del método

¿Puede el montón todavía apuntar al área del método?
Tenga en cuenta que el área de método contendrá información de clase y habrá objetos en el montón, entonces, ¿cómo sabe qué clase creó el objeto?
inserte la descripción de la imagen aquí

Pensamiento:
¿Cómo sabe un objeto de qué clase se creó? ¿Cómo grabarlo? Esto requiere comprender la información específica de un objeto Java.

2.3.4.4 Modelo de memoria de objetos de Java

Un objeto Java consta de 3 partes en la memoria: encabezado de objeto, datos de instancia y relleno de alineación
inserte la descripción de la imagen aquí

2.4 Modelo de memoria JVM

2.4.1 y área de datos de tiempo de ejecución

Lo anterior describe mucho sobre el área de datos de tiempo de ejecución. De hecho, el enfoque del almacenamiento de datos es el montón y el área de método (no el montón), por lo que el diseño de la memoria también se enfoca en estas dos áreas (tenga en cuenta que estas dos áreas son compartidas). por hilos).
Para la pila de la máquina virtual, la pila de métodos nativos y el contador del programa son todos de subprocesos privados.
Se puede entender que el área de datos de tiempo de ejecución de JVM es una especificación, y el modelo de memoria de JVM es la implementación de la especificación.

2.4.2 Visualización gráfica

Una es el área que no es de montón y la otra es el área de montón. El
área de montón se divide en dos bloques, uno es el área antigua y el otro es el área joven. El área joven se
divide en dos bloques, uno es el sobreviviente (S0 + S1), y el otro es el área de Edén.
S0 y S1 son lo mismo Grande, también se puede llamar Desde y Hasta
inserte la descripción de la imagen aquí

2.4.3 Proceso de creación de objetos

En general, los objetos recién creados se asignarán al área Edén y algunos objetos grandes especiales se asignarán directamente al área Antigua.

我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的。
inserte la descripción de la imagen aquí

2.4.4 常见问题

如何理解Minor/Major/Full GC

Minor GC:新生代
Major GC:老年代
Full GC:新生代+老年代

为什么需要Survivor区?只有Eden不行吗?

Si no hay Superviviente, cada vez que se realice un GC Menor en el área del Edén, los objetos supervivientes serán enviados a la vejez.
De esta manera, la vejez se llena rápidamente y se activa el GC mayor (porque el GC mayor suele ir acompañado del GC menor, que también puede considerarse como desencadenante del GC completo).
El espacio de memoria de la generación anterior es mucho mayor que el de la nueva generación, y lleva mucho más tiempo realizar un GC completo que un GC secundario.
¿Cuál es la desventaja del tiempo de ejecución prolongado? El GC completo frecuente consume mucho tiempo, lo que afectará la ejecución y la velocidad de respuesta de los programas grandes.
Podría decir, luego aumente o disminuya el espacio en la generación anterior.
Si se aumenta el espacio de la generación anterior, más objetos sobrevivientes pueden llenar la generación anterior. Aunque la frecuencia de Full GC se reduce, a medida que aumenta el espacio en la vejez, una vez que se produce Full GC, tardará más en ejecutarse.
Si se reduce el espacio de la generación anterior, aunque se reduzca el tiempo requerido para la GC completa, la generación anterior pronto se llenará con objetos supervivientes y aumentará la frecuencia de la GC completa.
Por lo tanto, la importancia de la existencia de Survivor es reducir la cantidad de objetos enviados a la generación anterior, lo que reduce la ocurrencia de Full GC. La preselección de Survivor garantiza que solo se enviarán los objetos que puedan sobrevivir en la nueva generación después de 16 Minor GC. a la nueva generación vejez.

¿Por qué necesitamos dos zonas de supervivencia?

最大的好处就是解决了碎片化。也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置
Survivor区。假设现在只有一个Survivor,我们来模拟一下流程:
刚刚新建的对象在Eden,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor
区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,EdenSurvivor各有一些
存活对象,如果此时把Eden区的存活对象硬放到Survivor,很明显这两部分对象所占有的内存是不连续的,
也就导致了内存碎片化。
永远有一个Survivor space是空的,另一个非空的Survivor space无碎片。

¿Por qué está Eden:S1:S2 8:1:1 en la nueva generación?

Memoria disponible en la nueva generación: la memoria garantizada por el algoritmo de copia es 9: 1. El
área Eden: S1 en la memoria disponible es 8: 1,
es decir, Eden: S1: S2 = 8: 1: 1 en la nueva generación
Las máquinas virtuales comerciales modernas están utilizando este algoritmo de recolección para reciclar la nueva generación, la investigación especial de IBM muestra que alrededor del 98% de los objetos en la nueva generación son
"vivos y muertos".

¿Es la memoria del montón un área compartida por subprocesos?

De forma predeterminada, la JVM abre un área de búfer en Eden para que cada hilo acelere la asignación de objetos, llamado TLAB, nombre completo:
Búfer de asignación local de subprocesos.
Los objetos se asignan primero en TLAB, pero el espacio TLAB suele ser relativamente pequeño. Si el objeto es relativamente grande, todavía se asigna en el área compartida.

2.4.5 Experiencia y Verificación

2.4.5.1 Uso de visualvm

Enlace de descarga del complemento Visualgc: https://visualvm.github.io/pluginscenters.html
Seleccione el enlace de la versión JDK correspondiente—>Herramientas—>Visual GC
Si el enlace anterior no puede encontrar uno adecuado, también puede descargar la versión correspondiente en línea

2.4.5.2 Desbordamiento de memoria de almacenamiento dinámico

el código

@RestController
public class HeapController {
    
    
	List<Person> list=new ArrayList<Person>();
	@GetMapping("/heap")
	public String heap(){
    
    
		while(true){
    
    
			list.add(new Person());
		}
	}
}

Recuerde establecer parámetros como -Xmx20M -Xms20M
resultados de ejecución
Visite: http://localhost:8080/heap

Excepción en el subproceso "http-nio-8080-exec-2" java.lang.OutOfMemoryError: se excedió el límite de sobrecarga de GC

2.4.5.3 Desbordamiento de memoria en el área de métodos


Por ejemplo, agregue información de clase , dependencias asm y códigos de clase al área de métodos.

<dependency>
	<groupId>asm</groupId>
	<artifactId>asm</artifactId>
	<version>3.3.1</version>
</dependency>
public class MyMetaspace extends ClassLoader {
    
    
	public static List<Class<?>> createClasses() {
    
    
	List<Class<?>> classes = new ArrayList<Class<?>>();
	for (int i = 0; i < 10000000; ++i) {
    
    
		ClassWriter cw = new ClassWriter(0);
		cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
		"java/lang/Object", null);
		MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
		"()V", null, null);
		mw.visitVarInsn(Opcodes.ALOAD, 0);
		mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
		"<init>", "()V");
		mw.visitInsn(Opcodes.RETURN);
		mw.visitMaxs(1, 1);
		mw.visitEnd();
		Metaspace test = new Metaspace();
		byte[] code = cw.toByteArray();
		Class<?> exampleClass = test.defineClass("Class" + i, code, 0,
		code.length);
		classes.add(exampleClass);
		}
	return classes;
	}
}

el código

@RestController
public class NonHeapController {
    
    
	List<Class<?>> list=new ArrayList<Class<?>>();
	@GetMapping("/nonheap")
	public String nonheap(){
    
    
		while(true){
    
    
			list.addAll(MyMetaspace.createClasses());
		}
	}
}

Establezca el tamaño de Metaspace, como -XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M visita de
resultados en ejecución ->http://localhost:8080/nonheap

java.lang.OutOfMemoryError: Metaspace en
java.lang.ClassLoader.defineClass1 (método nativo) ~[na:1.8.0_191] en
java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_191]

2.4.5.4 Pila de máquinas virtuales

Demostración de códigoStackOverFlow

public class StackDemo {
    
    
	public static long count=0;
	public static void method(long i){
    
    
	System.out.println(count++);
		method(i);
	}
	public static void main(String[] args) {
    
    
		method(1);
	}
}

resultado de la operación
inserte la descripción de la imagen aquí

ilustrar

Stack Space se usa para insertar en Stack Frame (marco de pila) al realizar llamadas recursivas de métodos. Entonces, cuando la llamada recursiva es demasiado profunda, es posible quedarse sin espacio de pila y causar un error de desbordamiento de pila.
-Xss128k: establece el tamaño de la pila para cada subproceso. Después de JDK 5, el tamaño de la pila de cada subproceso es de 1 M, y antes el tamaño de la pila de cada subproceso era de 256 K.

Ajuste de acuerdo con el tamaño de memoria requerido por los subprocesos de la aplicación. Bajo la misma memoria física, reducir este valor puede generar más hilos. Sin embargo, el sistema operativo todavía tiene un límite en la cantidad de subprocesos en un proceso y no se puede generar infinitamente. El valor de la experiencia es de alrededor de 3000~5000.

El tamaño de la pila de subprocesos es un arma de doble filo. Si se establece demasiado pequeño, puede ocurrir un desbordamiento de la pila, especialmente cuando hay recursión y bucles grandes en el subproceso, la posibilidad de desbordamiento es mayor. Si el valor se establece demasiado grande, habrá Afecta el número de pilas creadas Si se trata de una aplicación de subprocesos múltiples, se producirá un error de desbordamiento de memoria.

2.5 Garbage Collect (recolección de basura)

Se dijo antes que hay recolección de basura en la memoria del montón, como Minor GC en el área de Young, Major GC en el área de Old y Full GC en el área de Young y Old.
Pero para un objeto, ¿cómo se puede determinar que es basura? ¿Es necesario reciclarlo? ¿Cómo reciclarlo? Todavía tenemos que explorar estos temas en detalle.
Debido a que Java realiza automáticamente la gestión de la memoria y la recolección de elementos no utilizados, si no comprende todos los aspectos de la recolección de elementos no utilizados, será difícil
para nosotros procesarlos Clasificar y juzgar,
encontrar los objetos que están en uso y los objetos que ya no se usan, y luego borrar del montón aquellos objetos que no se usarán.

2.5.1 ¿Cómo determinar si un objeto es basura?

Para realizar la recolección de basura, primero debe saber qué tipo de objetos son basura.

2.5.1.1 Conteo de referencia

Para un objeto, siempre que la aplicación contenga una referencia al objeto, significa que el objeto no es basura.Si un objeto no tiene ninguna referencia de puntero a él, es basura.
Desventaja : si AB tiene referencias entre sí, nunca se reciclará.

2.5.1.2 Análisis de accesibilidad

A través del objeto de GC Root, comience a mirar hacia abajo para ver si se puede alcanzar un objeto
inserte la descripción de la imagen aquí

Se puede usar como raíz de GC: cargador de clases, subproceso, tabla de variables locales de la pila de máquinas virtuales, miembros estáticos, referencias constantes,
variables de la pila de métodos locales, etc.

Objetos a los que se hace referencia en la pila de la máquina virtual (tabla de variables locales en el marco de la pila).
El objeto al que hace referencia la propiedad estática de la clase en el área del método.
El objeto al que hace referencia la constante en el área del método.
El objeto al que hace referencia JNI (es decir, el método nativo en general) en la pila de métodos locales.

2.5.2 ¿Cuándo ocurrirá la recolección de basura?

La JVM completa automáticamente el GC, según el entorno del sistema de la JVM, por lo que el tiempo es incierto.
Por supuesto, podemos realizar manualmente la recolección de elementos no utilizados, como llamar al método System.gc() para notificar a la JVM que realice una recolección de elementos no utilizados, pero no podemos
controlar cuándo se ejecutará. Es decir, System.gc() es solo un aviso para reciclar, y la JVM determina cuándo reciclar
. Sin embargo, no se recomienda llamar a este método de forma manual, ya que los recursos consumidos por GC son relativamente grandes.

(1) Cuando el área Eden o el área S no es suficiente
(2) El espacio de generación anterior no es suficiente
(3) El área de método no es suficiente
(4) System.gc()

2.5.3 Algoritmo de recolección de basura

Luego de poder determinar que un objeto es basura, lo siguiente a considerar es reciclar ¿Cómo reciclar? Debe haber un algoritmo correspondiente
A continuación se presentan algoritmos comunes de recolección de basura.
2.5.3.1 Mark-Sweep (Mark-Sweep)
Mark
Descubra los objetos en la memoria que necesitan ser reclamados y márquelos
En este momento, todos los objetos en el montón serán escaneados para determinar los objetos que necesitan ser reclamados , que lleva mucho tiempo
inserte la descripción de la imagen aquí

Borrar
Limpiar los objetos que están marcados para ser reciclados, y liberar el espacio de memoria correspondiente
inserte la descripción de la imagen aquí

defecto

Después de borrar la marca, se generará una gran cantidad de fragmentos de memoria discontinuos. Demasiada fragmentación del espacio puede causar que cuando el programa
necesite asignar objetos grandes en el futuro, no pueda encontrar suficiente memoria continua y tenga que desencadenar otra acción de recolección de elementos no utilizados. por adelantado.
(1) Los dos procesos de marcar y borrar consumen mucho tiempo y son ineficientes
(2) Se generará una gran cantidad de fragmentos de memoria discontinuos. Demasiada fragmentación del espacio puede conducir a la incapacidad de asignar objetos grandes cuando el programa se está ejecutando. suficiente memoria contigua y tiene que desencadenar otra acción de recolección de elementos no utilizados antes.

2.5.3.2 Copia de marcas

Divida la memoria en dos áreas iguales y use solo una de ellas a la vez, como se muestra en la siguiente figura:
inserte la descripción de la imagen aquí

Cuando se agote una parte de la memoria, copie el objeto sobreviviente en otra parte y luego borre el espacio de memoria usado a la vez.
inserte la descripción de la imagen aquí

Desventajas: Reducción de la utilización del espacio.

2.5.3.3 Marca-Compacto

El algoritmo de recopilación de copias realizará más operaciones de copia cuando la tasa de supervivencia del objeto sea alta y la eficiencia será menor. Más importante aún, si no
desea desperdiciar el 50 % del espacio, debe tener espacio adicional para las garantías de asignación para lidiar con la situación extrema en la que todos los objetos en la memoria utilizada están
100 % vivos, por lo que la generación anterior generalmente no puede elegir directamente este algoritmo.

El proceso de marcado sigue siendo el mismo que el algoritmo "mark-clear", pero en lugar de limpiar directamente los objetos reciclables, los pasos posteriores mueven todos los objetos sobrevivientes a un extremo y luego limpian directamente la memoria fuera del límite final.

De hecho, en comparación con el "algoritmo de copia", el proceso anterior carece de un "área reservada"

inserte la descripción de la imagen aquí

Deje que todos los objetos sobrevivientes se muevan hacia un extremo y limpie la memoria más allá del límite.
inserte la descripción de la imagen aquí

2.5.4 Algoritmo de recolección generacional

Ahora que los 3 algoritmos de recolección de basura se presentan arriba, ¿cuál debería usarse en la memoria del montón?

Área joven: algoritmo de copia (después de asignar el objeto, el ciclo de vida puede ser relativamente corto y la eficiencia de copia del área joven es relativamente alta)
Área antigua: borrado de marcas o finalización de marcas (el tiempo de supervivencia del objeto en el área antigua el área es relativamente larga, no es necesario copiar, es mejor hacer una marca y limpiar)

2.5.5 Recolector de basura

Si el algoritmo de recolección es la metodología de recuperación de memoria, entonces el recolector de basura es la implementación específica de recuperación de memoria.
inserte la descripción de la imagen aquí

2.5.5.1 Serie

El recopilador Serial es el recopilador más básico con el historial de desarrollo más largo. Solía ​​ser (antes de JDK1.3.1) la única opción para recopilar en la nueva generación de máquinas virtuales.
Es un recolector de subproceso único, lo que no solo significa que solo usará una CPU o un subproceso de recolección para completar la recolección de elementos no utilizados, sino que, lo que es más importante, debe suspender otros subprocesos durante la recolección de elementos no utilizados.

Ventajas: Simple y eficiente, con alta eficiencia de recopilación de un solo subproceso
Desventajas: El proceso de recopilación debe suspender todos los subprocesos
Algoritmo: Algoritmo de copia
Ámbito de aplicación: Nueva generación
Aplicación: Recopilador predeterminado de nueva generación en modo Cliente

inserte la descripción de la imagen aquí

2.5.5.2 Serie antigua

El recopilador Serial Old es una versión antigua del recopilador Serial, y también es un recopilador de un solo subproceso. La diferencia es que utiliza el "algoritmo de clasificación de marca", y el proceso de operación es el mismo que el recopilador
Serial .
inserte la descripción de la imagen aquí

2.5.5.3 ParNuevo

Este recopilador puede entenderse como una versión de subprocesos múltiples del recopilador Serial.

Ventajas: Es más eficiente que Serial cuando hay múltiples CPU.
Desventajas: el proceso de recopilación suspende todos los subprocesos de la aplicación y la eficiencia es peor que Serial cuando se usa una sola CPU.
Algoritmo: Algoritmo de copia
Ámbito de aplicación: Nueva generación
Aplicación: El colector de nueva generación preferido en máquinas virtuales que se ejecutan en modo Servidor

inserte la descripción de la imagen aquí

2.5.5.4 Barrido paralelo

El recopilador Parallel Scavenge es un recopilador de nueva generación. También es un recopilador que utiliza el algoritmo de replicación y un recopilador paralelo de subprocesos múltiples. Tiene el mismo aspecto que ParNew, pero Parallel Scanvenge presta más atención al rendimiento del sistema.

Rendimiento = tiempo para ejecutar el código de usuario/(tiempo para ejecutar el código de usuario + tiempo de recolección de elementos no utilizados)
Por ejemplo, si la máquina virtual se ejecuta durante 100 minutos en total y el tiempo de recolección de elementos no utilizados tarda 1 minuto, el rendimiento = (100-1)/ 100=99% .
Si el rendimiento es mayor, significa que el tiempo de recolección de basura es más corto y el código de usuario puede hacer un uso completo de los recursos de la CPU para completar las tareas de cálculo del programa lo antes posible.

-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCRatio直接设置吞吐量的大小。

2.5.5.5 Paralelo Antiguo

El recopilador Parallel Old es una versión antigua del recopilador Parallel Scavenge. Utiliza algoritmos de subprocesos múltiples y clasificación por marca para la recolección de elementos no utilizados, y también presta más atención al rendimiento del sistema.

2.5.4.6 CMS

Sitio web oficial: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mark_sweep_cms_collector

El colector CMS (Concurrent Mark Sweep) es un colector que tiene como objetivo obtener el menor tiempo de pausa de recuperación.
Se utiliza el "algoritmo de borrado de marcas" y todo el proceso se divide en 4 pasos

(1) Marca inicial Marca inicial CMS Marca GC Roots para asociar objetos directamente, sin Tracing, la velocidad es muy rápida ( 2)
Marca concurrente Marca concurrente CMS para GC Roots Tracing
(3) Re-mark CMS remark Modificar el contenido de las marcas concurrentes debido a cambios en los programas de usuario
(4) La limpieza simultánea del barrido concurrente de CMS borra el espacio de recuperación de objetos inalcanzables, y al mismo tiempo se genera nueva basura, que se denomina basura flotante para la próxima limpieza

Dado que el subproceso del recopilador puede trabajar con subprocesos de usuario durante todo el proceso de marcado y borrado simultáneos, en términos generales, el
proceso de recuperación de memoria del recopilador CMS se ejecuta simultáneamente con los subprocesos de usuario.
inserte la descripción de la imagen aquí

2.5.5.7 G1 (basura primero)

Sitio web oficial: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection

Cuando se utiliza el recopilador G1, el diseño de la memoria del montón de Java es muy diferente al de otros recopiladores. Divide todo el montón de Java en múltiples
regiones independientes (Regiones) de igual tamaño, aunque todavía hay generación nueva y generación anterior. Concepto, pero la nueva generación y la vieja generación
ya no están físicamente aisladas, son una colección de una parte de la Región (no necesita ser continua).

El tamaño de cada Región es el mismo, que puede tener un valor entre 1M y 32M, pero se debe garantizar que sea la n-ésima potencia de 2.
Si el objeto es demasiado grande para caber en una Región [más del 50% del Tamaño de la región], entonces será directamente Póngalo en H
y establezca el Tamaño de la región: -XX:G1HeapRegionSize=M
El llamado Garbage-Frist es en realidad para dar prioridad al área de la Región con la mayor cantidad de basura.

(1) Recopilación generacional (todavía conserva el concepto de generación)
(2) Integración espacial (pertenece al algoritmo "mark-sort" como un todo, y no causará la fragmentación del espacio)
(3) Pausa predecible (más avanzada que CMS El punto es que el usuario puede especificar claramente un segmento de tiempo de M milisegundos de longitud, y el tiempo dedicado a la recolección de basura no debe exceder los N milisegundos)

inserte la descripción de la imagen aquí

El proceso de trabajo se puede dividir en los siguientes pasos:
el marcado inicial (Initial Marking) marca los objetos que se pueden asociar con las siguientes raíces de GC, y para modificar el valor de TAMS, los
subprocesos de usuario deben suspenderse. ) lleva a cabo un análisis de accesibilidad desde GC Roots para averiguar Los objetos supervivientes se ejecutan simultáneamente con el subproceso del usuario
. El marcado final corrige los datos modificados debido a la ejecución simultánea del programa del usuario durante la fase de marcado concurrente. El subproceso del usuario debe suspenderse para Examine y
recicle (recuento y evacuación de datos en vivo) el valor de recuperación de cada región. Ordene con el costo y haga un plan de recuperación de acuerdo con el tiempo de pausa esperado del GC del usuario.
inserte la descripción de la imagen aquí

2.5.5.8 ZGC

Sitio web oficial: https://docs.oracle.com/en/java/javase/11/gctuning/z-garbage-collector1.html#GUID-A5A42691-095E-47BA-B6DC-FB4E5FAA43D0

El nuevo colector ZGC introducido por JDK11, ya sea física o lógicamente, el concepto de nuevas y viejas edades ya no existe en ZGC. Se dividirá en páginas.
Cuando se realicen operaciones de GC, las páginas se comprimirán, por lo que hay no hay problema de fragmentación
Se puede usar en Linux de 64 bits, y todavía se usa relativamente raramente en la actualidad

(1) Puede cumplir con el requisito de tiempo de pausa dentro de los 10 ms
(2) Admite memoria de nivel de TB
(3) Después de que la memoria del montón aumenta, el tiempo de pausa aún está dentro de los 10 ms

inserte la descripción de la imagen aquí

2.5.5.9 Clasificación del Recolector de Basura

Serial Collector -> Serial y Serial Old
solo pueden ser ejecutados por un subproceso de recolección de elementos no utilizados, y el subproceso de usuario se suspende.
Adecuado para dispositivos integrados con memoria relativamente pequeña.

Recolector paralelo [prioridad de rendimiento] - > Parallel Scanvenge, Parallel Old
Múltiples subprocesos de recolección de elementos no utilizados funcionan en paralelo, pero el subproceso del usuario todavía está en estado de espera en este momento.

El marcado inicial (Initial Marking)
marca los siguientes objetos que se pueden asociar con GC Roots, y para modificar el valor de TAMS, el subproceso de usuario debe suspenderse

El marcado concurrente
lleva a cabo un análisis de accesibilidad desde GC Roots, encuentra objetos sobrevivientes y los ejecuta simultáneamente con los
subprocesos de los usuarios.

Marcado final (Marcado final)
corrige los datos que cambian debido a la ejecución concurrente de programas de usuario durante la fase de marcado concurrente, y el subproceso de usuario debe suspenderse

La detección y recuperación (recuento de datos en vivo y evacuación)
clasifica el valor y el costo de recuperación de cada región y formula un plan de recuperación basado en el tiempo de pausa esperado del GC del usuario. Es
adecuado para escenarios interactivos como la computación científica y el procesamiento en segundo plano.

Recolector concurrente
[prioridad de tiempo de pausa] -> CMS, el subproceso de usuario G1 y el subproceso de recolección de elementos no utilizados se ejecutan al mismo tiempo (pero no necesariamente en paralelo, se pueden ejecutar alternativamente), y el subproceso de recolección de elementos no utilizados no pausará el subproceso de usuario durante la ejecución correr.
Aplicable a escenarios que requieren tiempo relativo, como la Web.

2.5.5.10 Preguntas frecuentes

Rendimiento y tiempo de pausa
Tiempo de pausa -> Recolector de basura Respuesta de ejecución de la aplicación final de recolección de basura Rendimiento
-> Tiempo de código de usuario en ejecución / (Tiempo de código de usuario en ejecución + Tiempo de recolección de basura)

Cuanto más corto sea el tiempo de pausa, más adecuado para los programas que necesitan interactuar con los usuarios. Una buena velocidad de respuesta puede mejorar la experiencia del usuario; un
alto rendimiento puede usar eficientemente el tiempo de la CPU y completar las tareas de cálculo del programa lo antes posible. Es principalmente adecuado para cálculos de fondo sin demasiadas tareas interactivas múltiples.

Resumen : Estos dos indicadores son también los criterios para evaluar los beneficios del recolector de basura.

Cómo elegir el recolector de basura adecuado
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28

Priorice el ajuste del tamaño del montón y deje que el servidor elija por sí mismo.
Si la memoria es inferior a 100M, use el recopilador en serie
. Si es un solo núcleo y no hay un requisito de tiempo de pausa, use en serie o JVM
. Si la pausa se permite que el tiempo exceda 1 segundo, elija paralelo o JVM Elija usted mismo
Si el tiempo de respuesta es lo más importante y no puede exceder 1 segundo, use el recopilador concurrente

Para la colección G1
, JDK 7 comenzó a usarse, JDK 8 es muy maduro y el recolector de basura predeterminado de JDK 9 es adecuado para las generaciones nuevas y antiguas.
¿Se utiliza el colector G1?

(1) Más del 50% del montón está ocupado por objetos supervivientes
(2) La velocidad de asignación y promoción de objetos varía mucho
(3) El tiempo de recolección de elementos no utilizados es relativamente largo

El nombre completo de RSet en G1
es Recorded Set, que registra y mantiene la relación de referencia de los objetos en Region

Imagínese, cuando el recolector de basura G1 realiza la recolección de basura en la nueva generación, es decir, Minor GC, si la región hace referencia al objeto en la generación anterior, el objeto en la nueva generación no se puede reciclar en este momento, cómo grabarlo?
¿Qué tal esto? Use una estructura similar a hash, la clave registra la dirección de la región y el valor indica la colección que se refiere al objeto, para que pueda saber qué objetos en la generación anterior se refieren al objeto, para que no se puede reciclar.

Cómo habilitar el recolector de basura requerido

(1) Serie
-XX: +UseSerialGC
-XX: +UseSerialOldGC
(2) Paralelo (prioridad de rendimiento):
-XX: +UseParallelGC
-XX: +UseParallelOldGC
(3) Recopilador simultáneo (prioridad de tiempo de respuesta)
-XX: + UseConcMarkSweepGC
-XX :+UsarG1GC

inserte la descripción de la imagen aquí

3. En profundidad

3.1 Parámetros de JVM

3.1.1 Parámetros estándar

-versión
-ayuda
-servidor
-cp

inserte la descripción de la imagen aquí

3.1.2 Parámetro -X

Parámetros no estándar, es decir, pueden cambiar en cada versión de JDK

-Interpretación y ejecución de Xint
-Xcomp compila en código local cuando se usa por primera vez
-Modo mixto Xmixed, JVM decide por sí mismo

inserte la descripción de la imagen aquí

3.1.3 -XX parámetros

El tipo de parámetro más utilizado
Parámetros no estandarizados, relativamente inestables, utilizados principalmente para el ajuste y depuración de JVM

a.Formato de tipo booleano
: -XX:[±] + o - significa habilitar o deshabilitar el atributo de nombre
Por ejemplo: -XX:+UseConcMarkSweepGC significa habilitar el recolector de basura de tipo CMS
-XX:+UseG1GC significa habilitar el tipo G1 recolector de basura

b. Formato de tipo no booleano: -XX= indica que el valor del atributo de nombre es valor
Por ejemplo: -XX:MaxGCPauseMillis=500
-Xms1000M es equivalente a -XX:InitialHeapSize=1000M
-Xmx1000M es equivalente a -XX:MaxHeapSize =1000M
-Xss100, etc. Valor en -XX:ThreadStackSize=100

3.1.4 Otros parámetros

-Xms1000M es equivalente a -XX:InitialHeapSize=1000M
-Xmx1000M es equivalente a -XX:MaxHeapSize=1000M
-Xss100 es equivalente a -XX:ThreadStackSize=100

Entonces esta pieza también es equivalente a un parámetro de tipo -XX

3.1.5 Ver parámetros

java -XX:+PrintFlagsFinal -version > banderas.txt
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Vale la pena señalar que "=" indica el valor predeterminado y ":=" indica el valor modificado por el usuario o JVM. Si desea ver el valor
de un parámetro específico de un proceso, puede usar jinfo.
Generalmente, necesita configurar el parámetro. Puede verificar el parámetro actual primero. qué, luego modificar

3.1.6 Formas habituales de establecer parámetros

La configuración en herramientas de desarrollo como IDEA,
cuando eclipse ejecuta el paquete jar: java -XX:+UseG1GC xxx.jar
contenedor web como tomcat, se puede configurar en el script y ajustar
los parámetros de un proceso java en tiempo real a través de jinfo ( los parámetros solo se pueden marcar Los indicadores que son manejables se pueden modificar en tiempo real)

3.1.7 Prácticas y conversión de unidades

1 byte (byte) = 8 bits (bit)
1 KB = 1024 byte (byte)
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB

(1) Establezca el tamaño de la memoria del montón y el parámetro print
-Xmx100M -Xms100M -XX:+PrintFlagsFinal
(2) Consulte el valor de +PrintFlagsFinal
:=true
(3) Consulte el tamaño de la memoria del montón MaxHeapSize
:= 104857600
(4) Convierta
104857600( Byte)/ 1024=102400(KB)
102400(KB)/1024=100(MB)
(5) Conclusión
104857600 es una unidad de byte

3.1.8 Significados de los parámetros comunes

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

3.2 Comandos comunes

3.2.1 jps

Ver el proceso java

El comando jps enumera las máquinas virtuales Java HotSpot instrumentadas en el sistema de destino. El comando se limita a informar información sobre las JVM para las que tiene permisos de acceso.

inserte la descripción de la imagen aquí

3.2.2 jinfo

(1) Ver y ajustar los parámetros de configuración de JVM en tiempo real

El comando jinfo imprime información de configuración de Java para un proceso de Java específico o un archivo central o un servidor de depuración remoto. La información de configuración incluye las propiedades del sistema Java y los indicadores de línea de comandos de Java Virtual Machine (JVM).

(2) Ver uso
jinfo -flag nombre PID Ver el valor del atributo de nombre de un proceso java

jinfo -flag MaxHeapSize PID
jinfo -flag UseG1GC PID

inserte la descripción de la imagen aquí

(3) Modificar
parámetros Solo se pueden modificar en tiempo real las banderas marcadas como manejables

jinfo -flag [+|-] PID 
jinfo -flag <name>=<value> PID

(4) Ver algunos parámetros a los que se les han asignado valores

jinfo -flags PID

inserte la descripción de la imagen aquí

3.2.3 soporte

(1) Ver estadísticas de rendimiento de la máquina virtual

El comando jstat muestra estadísticas de rendimiento para una máquina virtual Java HotSpot instrumentada. La JVM de destino se identifica por su identificador de máquina virtual u opción vmid.

(2) Ver información de carga de clases

jstat -class PID 1000 10 Ver la información de carga de clase de un proceso java, generar una vez cada 1000 milisegundos y generar 10 veces en total

inserte la descripción de la imagen aquí

(3) Ver información de recolección de basura

jstat-gc PID 1000 10

inserte la descripción de la imagen aquí

3.2.4 jstack

(1) Ver información de la pila de subprocesos

El comando jstack imprime seguimientos de pila de Java de subprocesos de Java para un proceso de Java, un archivo central o un servidor de depuración remoto especificado.

(2) uso

jstack PID

inserte la descripción de la imagen aquí

(3) Solucionar problemas de interbloqueo
DeadLockDemo

//运行主类
public class DeadLockDemo
{
    
    
    public static void main(String[] args)
    {
    
    
        DeadLock d1=new DeadLock(true);
        DeadLock d2=new DeadLock(false);
        Thread t1=new Thread(d1);
        Thread t2=new Thread(d2);
        t1.start();
        t2.start();
    }
}
//定义锁对象
class MyLock{
    
    
    public static Object obj1=new Object();
    public static Object obj2=new Object();
}
//死锁代码
class DeadLock implements Runnable{
    
    
    private boolean flag;
    DeadLock(boolean flag){
    
    
        this.flag=flag;
    }
    public void run() {
    
    
        if(flag) {
    
    
            while(true) {
    
    
                synchronized(MyLock.obj1) {
    
    
                    System.out.println(Thread.currentThread().getName()+"----if获得obj1锁");
                    synchronized(MyLock.obj2) {
    
    
                        System.out.println(Thread.currentThread().getName()+"----if获得obj2锁");
                    }
                }
            }
        }
        else {
    
    
            while(true){
    
    
                synchronized(MyLock.obj2) {
    
    
                    System.out.println(Thread.currentThread().getName()+"----否则获得obj2锁");
                    synchronized(MyLock.obj1) {
    
    
                        System.out.println(Thread.currentThread().getName()+"----否则获得obj1锁");

                    }
                }
            }
        }
    }
}

Ejecutando el resultado
inserte la descripción de la imagen aquí
del análisis jstack
inserte la descripción de la imagen aquí
Lleve la información de impresión hasta el final para encontrar

imagen.png

3.2.5 jmap

(1) Generar una instantánea de volcado de montón

El comando jmap imprime mapas de memoria de objetos compartidos o detalles de memoria de almacenamiento dinámico de un proceso específico, archivo central o servidor de depuración remoto.

(2) Imprimir información relacionada con la memoria del montón

jmap -pila PID

jinfo -flag UsePSAdaptiveSurvivorSizePolicy 35352
-XX:SurvivorRatio=8

inserte la descripción de la imagen aquí

(3) Volcar la información relacionada con la memoria del montón

jmap -dump:format=b,file=heap.hprof PID

inserte la descripción de la imagen aquí

(4) Configure el volcado de producción para generar el archivo.
Generalmente, durante el desarrollo, se pueden agregar las siguientes dos oraciones a los parámetros de JVM, de modo que cuando la memoria se desborde, el archivo se volcará automáticamente
-XX:+HeapDumpOnOutOfMemoryError -XX: HeapDumpPath=montón.hprof

Establezca el tamaño de la memoria del montón: -Xms20M -Xmx20M
start, luego visite localhost:9090/heap, haciendo que la memoria del montón se desborde

Cuatro optimización del rendimiento

La optimización del rendimiento de JVM se puede dividir en nivel de código y nivel sin código.
En el nivel de código, puede combinar instrucciones de código de bytes para la optimización, como una declaración de bucle, que puede extraer código no relacionado con el bucle
del cuerpo del bucle, de modo que estos códigos no necesitan ejecutarse repetidamente en el nivel de código de bytes.
En el nivel sin código, en general, se puede optimizar en términos de uso de memoria, gc y cpu.
Tenga en cuenta que el ajuste de JVM es un proceso largo y complicado y, en muchos casos, no es necesario optimizar la JVM, ya que la propia JVM ha
realizado muchas operaciones de optimización interna.
Puede discutir la optimización de la JVM desde los tres aspectos de la memoria, gc y cpu, pero todos deben prestar atención para no sintonizar y sintonizar

4.1 Memoria

4.1.1 Asignación de memoria

En circunstancias normales, no es necesario configurarlo, ¿y qué si se trata de un escenario de promoción o venta flash?
Cada máquina está configurada con 2c4G, tomando como ejemplo 3000 pedidos por segundo, todo el proceso tiene una duración de 60 segundos
inserte la descripción de la imagen aquí

4.1.2 Falta de memoria (OOM)

Generalmente, hay dos razones:
(1) En el caso de una gran concurrencia
(2) Las fugas de memoria conducen a un desbordamiento de memoria

4.1.2.1 Gran concurrencia [Seckill]

Caché del navegador, caché local, código de verificación
CDN
clúster de servidor de recursos estáticos + equilibrio de carga
separación de recursos dinámicos y estáticos, límite actual [basado en el depósito de tokens, algoritmo de depósito con fugas]
caché a nivel de aplicación, límite de corriente anti-descarga de interfaz, cola, rendimiento de Tomcat optimización
asincrónica Middleware de mensajes
Redis hotspot caché de objetos de datos
Bloqueos distribuidos, bloqueos de bases de datos,
ausencia de pago en 5 minutos, cancelación de pedidos, restauración de inventario, etc.

4.1.2.2 Las fugas de memoria conducen a un desbordamiento de memoria

La fuga de memoria causada por ThreadLocal eventualmente conduce a un desbordamiento de memoria

public class TLController {
    
    
 @RequestMapping(value = "/tl")
 public String tl(HttpServletRequest request) {
    
    
     ThreadLocal<Byte[]> tl = new ThreadLocal<Byte[]>();
     // 1MB
     tl.set(new Byte[1024*1024]);
     return "ok";
 }
}

(1) Subir al servidor de Alibaba Cloud
jvm-case-0.0.1-SNAPSHOT.jar
(2) Iniciar

java -jar -Xms1000M -Xmx1000M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=jvm.hprof  jvm-case-0.0.1-SNAPSHOT.jar

(3) Use jmeter para simular 10000 concurrencia

39.100.39.63:8080/tl

(4) vista de comando superior

top
top -Hp PID

(5) jstack verifica la situación del subproceso y descubre que no hay interbloqueo o bloqueo de IO

jstack PID
java -jar arthas.jar   --->   thread

(6) Compruebe el uso de la memoria en montón y descubra que la tasa de uso de la memoria en montón ha alcanzado el 88,95%

jmap -heap PID
java -jar arthas.jar   --->   dashboard

(7) En este momento, se puede juzgar aproximadamente que se ha producido una fuga de memoria y se ha producido un desbordamiento de memoria, entonces, ¿cómo solucionarlo?

jmap -histo:live PID | more
获取到jvm.hprof文件,上传到指定的工具分析,比如heaphero.io

4.2 CG

Aquí tomamos el ajuste del recolector de basura G1 como ejemplo

4.2.1 Si elegir G1

Sitio web oficial: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases

(1) Más del 50% del montón está ocupado por objetos supervivientes
(2) La velocidad de asignación y promoción de objetos varía mucho
(3) El tiempo de recolección de elementos no utilizados es relativamente largo

4.2.2 Afinación G1

(1) Use el recolector de basura G1GC: -XX:+UseG1GC
para modificar los parámetros de configuración, obtener registros de gc y usar GCViewer para analizar el rendimiento y el tiempo de respuesta

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  99.16%         0.00016s         0.0137s        0.00559s          12

(2) Ajuste el tamaño de la memoria y luego obtenga un análisis de registro gc

-XX:MetaspaceSize=100M
-Xms300M
-Xmx300M

Por ejemplo, establezca el tamaño de la memoria del montón, obtenga el registro de gc y use GCViewer para analizar el rendimiento y el tiempo de respuesta.

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  98.89%          0.00021s        0.01531s       0.00538s           12

(3) Ajustar el tiempo máximo de pausa

-XX:MaxGCPauseMillis=200    设置最大GC停顿时间指标

Por ejemplo, establezca el tiempo de pausa máximo, obtenga registros de gc y use GCViewer para analizar el rendimiento y el tiempo de respuesta.

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  98.96%          0.00015s        0.01737s       0.00574s          12

(4) Porcentaje de uso de memoria en montón cuando se inicia GC concurrente

-XX:InitiatingHeapOccupancyPercent=45 
G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例。值为 0 则表示“一直执行GC循环)'. 默认值为 45 (例如, 全部的 45% 或者使用了45%).

Por ejemplo, configure el parámetro de porcentaje, obtenga el registro de gc y use GCViewer para analizar el rendimiento y el tiempo de respuesta.

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  98.11%          0.00406s        0.00532s       0.00469s          12

4.2.3 La mejor práctica de afinación G1

Sitio web oficial: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#r
recomendaciones

(1) No configure manualmente el tamaño de la nueva generación y la generación anterior , simplemente configure todo el montón El tamaño
por qué: https://blogs.oracle.com/poonam/increased-heap-usage-with-g1-gc

Durante la operación del colector G1, ajustará el tamaño de la nueva generación y la generación anterior por sí mismo.
De hecho, ajusta la velocidad y la antigüedad de la promoción del objeto a través del tamaño de la generación de adaptación, para lograr la pausa. objetivo de tiempo establecido para el colector.Si
el tamaño se establece manualmente, estará bien.Significa abandonar la sintonización automática de G1

(2) Sintonice continuamente el objetivo de tiempo de pausa

En circunstancias normales, es posible establecer este valor en 100 ms o 200 ms (será diferente en diferentes situaciones), pero no es razonable establecerlo en 50 ms. Si el tiempo de pausa es demasiado corto, hará que G1 no pueda mantener la velocidad de generación de basura. Eventualmente degenera en Full GC. Por lo tanto, el ajuste de este parámetro es un proceso continuo, ajustándose gradualmente al mejor estado. El tiempo de pausa es solo un objetivo y no siempre se puede cumplir.

(3) Utilice -XX:ConcGCThreads=n para aumentar el número de subprocesos marcados

Si el umbral de IHOP se establece demasiado alto, puede haber un riesgo de falla en la transferencia, como espacio insuficiente cuando se transfiere el objeto. Si el umbral se establece demasiado bajo, el ciclo de marcado se ejecutará con demasiada frecuencia y es posible que las colecciones mixtas no recuperen espacio.
Si el valor de IHOP se establece razonablemente, pero cuando el tiempo de ciclo simultáneo es demasiado largo, puede intentar aumentar la cantidad de subprocesos simultáneos y aumentar ConcGCThreads.

(4) Sintonización GC mixta

-XX:Porcentaje de ocupación de montón inicial
-XX:G1MixedGCLiveThresholdPercent
-XX:G1MixedGCCountTarger
-XX:G1OldCSetRegionThresholdPercent

(5) Aumentar adecuadamente el tamaño de la memoria del montón
(6) GC completo anormal

A veces se encuentra que cuando el sistema recién se inicia, se producirá un GC completo, pero el espacio en la generación anterior es relativamente suficiente, lo que generalmente es causado por el área Metaspace. Otros se pueden aumentar adecuadamente a través de MetaspaceSize, como 256M.

4.3 Guía de optimización del rendimiento de JVM

inserte la descripción de la imagen aquí

4.4 Ajuste previo para recolección de basura

1. Si la configuración de la memoria cumple con los requisitos de simultaneidad antes de conectarse, y si no cumple con los requisitos, debe ajustarse

Calcule a partir del diseño de la memoria del objeto, calcule el tamaño de la memoria del montón, luego calcule el uso de la memoria del montón por * tiempo de concurrencia y luego calcule si la memoria puede contenerlo, considerando el equilibrio de carga y la redundancia

2. ¿Cuál es el rendimiento de la prueba de presión, generalmente controlado a más del 95%?

En el caso de un rendimiento extremo, la desviación no supera el 1%, ajuste el tiempo de pausa, use la vista GC u otras herramientas GC para analizar

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  99.16%         0.00016s         0.0137s        0.00559s          12

3. La frecuencia de Full GC y Young GC, si es necesario reemplazar el colector GC

Por ejemplo, G1 mencionado anteriormente

4. Observación de confiabilidad: si habrá una pérdida de memoria, esto se puede observar, de acuerdo con el cambio de memoria

Se pueden realizar dos GC y luego, para los archivos volcados, analizar el movimiento de la memoria y el reciclaje de los archivos.

5. También se determina si los parámetros del recolector de basura deben cambiarse de acuerdo con el rendimiento y el GC

Esto se usa junto con el segundo caso.

6. Uso de la CPU

Analice interbloqueos y fugas de memoria

Interbloqueo: jstack

4.5 Preguntas frecuentes

(1) La diferencia entre pérdida de memoria y desbordamiento de memoria

Una fuga de memoria significa que los objetos que ya no se usan no se pueden reciclar de manera oportuna y continúan ocupando espacio en la memoria, lo que resulta en una pérdida de espacio en la memoria.
Las fugas de memoria pueden conducir fácilmente a un desbordamiento de memoria, pero el desbordamiento de memoria no es necesariamente causado por fugas de memoria.

(2) ¿El joven gc tendrá stw?

No importa qué GC, stop-the-world se enviará, la diferencia es el tiempo que ocurre. Y esta vez tiene algo que ver con el recolector de elementos no utilizados. Los recolectores Serial, PartNew y Parallel Scavenge suspenderán los subprocesos de los usuarios, ya sean en serie o paralelos, mientras que CMS y G1 no suspenderán los subprocesos de los usuarios
cuando se marquen simultáneamente. Pero en otras ocasiones, el el hilo del usuario se suspenderá y el tiempo para detener el mundo
es relativamente mucho más corto.

(3) La diferencia entre gc mayor y gc completo

Major GC es equivalente a Full GC en muchos materiales de referencia, y también podemos encontrar que solo hay Minor GC y Full GC en muchas herramientas de monitoreo de rendimiento. En circunstancias normales, un GC completo realizará la recolección de elementos no utilizados en la generación joven, la generación anterior, el metaespacio y la memoria fuera del montón. Hay muchas razones para activar Full GC: cuando la generación joven asciende al tamaño de objeto de la generación anterior y es más grande que el espacio restante en la generación anterior, se activará Full GC; cuando el uso de espacio de la generación anterior supera un cierto umbral, se activará Full GC; cuando el metaespacio sea insuficiente (la generación permanente de JDK1.7 es insuficiente), también se activará Full GC; cuando se llame a System.gc(), también se organizará un Full GC.

(4) ¿Qué es la memoria directa?

La biblioteca NIO de Java permite que los programas de Java usen memoria directa. La memoria directa es el espacio de memoria que se aplica directamente al sistema fuera del montón de Java. Por lo general, el acceso a la memoria directa es más rápido que el montón de Java. Por lo tanto, por consideraciones de rendimiento, la memoria directa puede considerarse en ocasiones con lecturas y escrituras frecuentes. Dado que la memoria directa está fuera del almacenamiento dinámico de Java, su tamaño no estará directamente limitado por el tamaño máximo del almacenamiento dinámico especificado por Xmx, pero la memoria del sistema está limitada y la suma del almacenamiento dinámico de Java y la memoria directa todavía está limitada por el valor máximo. que el sistema operativo puede proporcionar Memoria.

(5) Maneras de juzgar la basura

Método de recuento de referencias : significa que si se hace referencia al objeto en algún lugar, será +1, si no es válido, será -1 y se reciclará cuando sea 0. Sin embargo, la JVM
no porque no puede determinar referencias circulares mutuas (A hace referencia a B , B hace referencia al caso de A).
Método de la cadena de referencia : A juzgar por un objeto RAÍZ GC (objetos referenciados por variables estáticas en el área del método, etc. - variables estáticas), si hay
una cadena que puede llegar a la RAÍZ GC, significa que se puede reciclar.

(6) ¿Se deben reciclar los objetos inalcanzables?

Incluso los objetos inalcanzables en el método de análisis de accesibilidad no son "deben morir". Los objetos que son inalcanzables en el análisis de propiedades se marcan por primera vez y se filtran una vez, y la condición de filtrado es si es necesario ejecutar el método de finalización para este objeto. . Cuando el objeto no cubre el método de finalización, o la máquina virtual ha llamado al método de finalización, la máquina virtual considera estos dos casos como innecesarios para ejecutar.
Los objetos que se juzgue que necesitan ser ejecutados se colocarán en una cola para una segunda marca y, a menos que el objeto esté asociado con cualquier objeto en la cadena de referencia, en realidad se reciclará.

(7) ¿Por qué debemos distinguir entre la nueva generación y la vieja generación?

La recolección de basura actual de las máquinas virtuales adopta el algoritmo de recolección generacional, este algoritmo no tiene ideas nuevas, pero divide la memoria en varios bloques de acuerdo con los diferentes ciclos de vida de los objetos. Generalmente, el montón de Java se divide en la nueva generación y la generación anterior, para que podamos elegir el algoritmo de recolección de basura apropiado de acuerdo con las características de cada edad.
Por ejemplo, en la nueva generación, una gran cantidad de objetos morirán cada vez que se recopilen, por lo que puede elegir un algoritmo de copia y solo debe pagar una pequeña cantidad del costo de copia de objetos para completar cada recolección de elementos no utilizados. La probabilidad de supervivencia de los objetos en la vejez es relativamente alta y no hay espacio adicional para garantizar su asignación, por lo que debemos elegir el algoritmo "mark-clear" o "mark-compact" para la recolección de basura.

(8) ¿Cuál es la diferencia entre G1 y CMS?

CMS se enfoca principalmente en la colección de la generación anterior, mientras que G1 se enfoca en la colección de generaciones, incluyendo el Young GC de la generación joven y el Mix GC de la generación anterior;

G1 usa el método Region para dividir la memoria heap y lo implementa en base al algoritmo de clasificación de marcas, lo que reduce la generación de fragmentos de basura en su conjunto; en la fase de marca de inicialización, la Tabla de tarjetas utilizada para buscar objetos alcanzables se implementa en una manera diferente

(9) Reciclaje de clases inútiles en el área de método

El área de método recicla principalmente clases inútiles, entonces, ¿cómo juzgar si una clase es una clase inútil?
Es relativamente sencillo determinar si una constante es una "constante abandonada", pero las condiciones para determinar si una clase es una "clase inútil" son relativamente duras.
Una clase debe cumplir las siguientes tres condiciones al mismo tiempo para ser considerada como una "clase inútil":
a-Todas las instancias de esta clase han sido recicladas, es decir, no hay ninguna instancia de esta clase en el almacenamiento dinámico de Java.
b - El ClassLoader que cargó esta clase ha sido reciclado.
c- El objeto java.lang.Class correspondiente a esta clase no está referenciado en ninguna parte, y no se puede acceder al método de esta clase a través de la reflexión en ninguna parte.

Supongo que te gusta

Origin blog.csdn.net/lx9876lx/article/details/129150878
Recomendado
Clasificación