Memoria y recolección de basura de JVM Parte 2

3 Área de ejecución

3.1 Pila de métodos nativos

inserte la descripción de la imagen aquí

3.2 Contador de programa

inserte la descripción de la imagen aquí
El Registro de PC es la única área en la Especificación de Máquina Virtual de Java que no especifica ningún OutOfMemoryError.

¿Por qué hay un registro de PC?
Debido a que la CPU alternará entre diferentes subprocesos, después de volver, necesita saber dónde comenzar a ejecutar.
El intérprete de código de bytes de JVM necesita cambiar el valor en el registro de la PC para aclarar qué instrucción de código de bytes debe ejecutarse a continuación.

¿Por qué el registro de la PC debe configurarse como subproceso privado?
Para registrar con precisión la dirección del código de bytes que ejecuta cada subproceso, la mejor solución es asignar un registro de PC para cada subproceso.

3.3 Área de métodos

inserte la descripción de la imagen aquí
Un espacio de memoria separado del montón.

El tamaño del área de métodos determina cuántas clases puede guardar el sistema. Si el sistema define demasiadas clases y el área de métodos se desborda, la máquina virtual también generará un OutOfMemoryError. Por ejemplo, se carga una gran cantidad de paquetes jar de terceros; Tomcat implementa demasiados proyectos. Un gran número de clases de reflexión generadas.

Información de tipo de tienda , constantes, variables estáticas, caché de código compilado por el compilador justo a tiempo, etc.
[Información de tipo]
(1) El nombre efectivo completo de este tipo (nombre calificado completo = nombre del paquete. Nombre de clase) (2) El nombre efectivo completo de la
clase principal directa de este tipo (interfaz o java . (2) La información relacionada con el dominio incluye: nombre de dominio, tipo, modificador (subconjunto de transitorio volátil final estático protegido público privado), etc. [Información del método] (1) Nombre del método, tipo de retorno, número y tipo de parámetros en orden, modificadores del método (2) Código de bytes del método, longitud de la tabla de variables locales, información estática de la tabla de excepciones.







[Variables de clase no finales]
Las variables de clase son compartidas por todas las instancias y se puede acceder a ellas incluso cuando no hay una instancia.

pubic class MethodAreaTest {
    
    
	public static void main(String[] args) {
    
    
		Order order = null;
		// 不会有空指针异常
		order.hello();
		System.out.println(order.count);
	}
}

class Order {
    
    
	public static int count = 1;
	public static void hello() {
    
    
		System.out.println("hello");
	}
}

3.3.1 Evolución del área de métodos en Hotspot

Jdk7 solía ser una generación permanente, y la memoria en la JVM utilizada
después de que JDK8 usa metaespacio. El metaespacio no está en la memoria establecida por la máquina virtual, pero usa la memoria local .

3.3.2 Establecer el tamaño de la memoria del área de método

【java 7】
https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html
https://docs.oracle.com/cd/E74363_01/ohi_vbp_-_installation_guide–20160224-094432-html-chunked/s66.html
https://docs.oracle.com/javase/7/docs/tech notas/herramientas/windows/java.html

-XX:PermSize Establece el espacio de asignación inicial de generación permanente. El valor predeterminado es 20,75 M. (Song Hongkang)

Opción y valor predeterminado Descripción
-XX:TamañoMáximoPerm=64m Tamaño de la generación permanente [5.0 y posteriores: las máquinas virtuales de 64 bits se escalan un 30 % más grandes; 1.4 amd64: 96 m; 1.3.1-cliente: 32 m]. Las máquinas de 64 bits tienen por defecto 82M (Song Hongkang)
【Java 8】
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

XX:MetaspaceSize=tamaño
Establece el tamaño del espacio de metadatos de clase asignado que desencadenará una recolección de elementos no utilizados la primera vez que se exceda. Este umbral para una recolección de elementos no utilizados aumenta o disminuye según la cantidad de metadatos utilizados. El tamaño predeterminado depende de la plataforma.
ventanas下默认值是21M

-XX:MaxMetaspaceSize=el tamaño
no está limitado de forma predeterminada. -1. establecer case-XX:MaxMetaspaceSize=256m

3.3.3 Conjunto de constantes de tiempo de ejecución

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

La tabla de pool constante se incluirá en el archivo de bytecode. La tabla de pool de constantes contiene varios literales, referencias simbólicas a tipos, campos y métodos.
El conjunto de constantes de tiempo de ejecución forma parte del área de métodos. El grupo de constantes se coloca en el grupo de constantes de tiempo de ejecución después de cargar la clase. El grupo de constantes de tiempo de ejecución es dinámico.

3.3.4 Ejemplo de uso del área de método

https://www.bilibili.com/video/BV1PJ411n7xZ?p=96&spm_id_from=pageDriver&vd_source=f4dcb991bbc4da0932ef216329aefb60

3.3.5 Evolución del área de métodos

Cambios en el área de métodos en HotSpot:

Antes de Jdk 1.6 Hay una generación permanente (generación permanente), y las variables estáticas se almacenan en la generación permanente
JDK 1.7 Hay una generación permanente, pero se ha "eliminado gradualmente de la generación permanente". El grupo de constantes de cadena y las variables estáticas se eliminan de la generación permanente y se almacenan en el montón.
JDK 1.8 Ninguna generación permanente, tipo de información, campos, métodos y constantes se almacenan en el metaespacio (el metaespacio está en la memoria local), pero los conjuntos de constantes de cadena y las variables estáticas aún se almacenan en el montón

[Razones para reemplazar la generación permanente por el metaespacio]
(1) Es difícil determinar el tamaño del espacio de la generación permanente.
En proyectos como la ingeniería web, hay muchas clases cargadas dinámicamente, que son propensas a OOM de generación permanente. El metaespacio no está en la máquina virtual, utiliza la memoria local y el tamaño del metaespacio solo está limitado por la memoria local.
(2) Es difícil optimizar la generación permanente.
La recolección de basura del área de método recicla principalmente dos partes: constantes obsoletas en el grupo de constantes y tipos que ya no se usan.
Cómo determinar que una clase ya no se usa es más difícil.

[La razón por la cual la tabla de cadenas se coloca en el montón]
La eficiencia de recopilación de generación permanente es muy baja y solo se activará durante el gc completo. Sin embargo, se creará una gran cantidad de cadenas durante el proceso de desarrollo y la baja eficiencia de recuperación conducirá a una memoria insuficiente en la generación permanente.

[¿Dónde existe la variable estática?]
La entidad de objeto a la que se hace referencia estáticamente siempre está en el espacio de almacenamiento dinámico. La instancia de objeto creada por new debe estar en el espacio de almacenamiento dinámico.
La máquina virtual HotSport de JDK 7 y versiones posteriores elige almacenar variables estáticas y objetos Class asignados en la sección de lenguaje Java juntos, y almacenarlos en el montón de Java.

3.3.5 Recolección de basura en el área de método

No hay ningún requisito en la "Especificación de máquina virtual de Java" para GC el área de método.
ZGC no admite la descarga de clases

3.4 pila

La pila se ejecuta y la pila se almacena.

Hilo privado. El ciclo de vida es consistente con el hilo.

Los 8 tipos básicos de variables + variables de referencia de objetos + métodos de instancia se asignan en la memoria de pila de la función.

Un marco de pila corresponde a un método Java.

La pila es un método de almacenamiento rápido y eficiente, y su velocidad de acceso solo es superada por el contador del programa. No hay ningún problema de GC o OOM en la pila.
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
La especificación de la máquina virtual de Java permite que el tamaño de la pila de Java sea dinámico o fijo .
Si se utiliza una pila de máquina virtual de tamaño fijo, la capacidad de pila de cada subproceso se puede seleccionar de forma independiente cuando se crea el subproceso. Si la capacidad de la pila solicitada por el subproceso supera la capacidad máxima permitida por la pila de la máquina virtual, la máquina virtual Java genera un archivo StackOverFlowError.
Si la pila de la máquina virtual Java se puede expandir dinámicamente y no puede solicitar suficiente memoria al intentar expandirse, o no hay suficiente memoria para crear la pila de la máquina virtual correspondiente al crear un nuevo subproceso, entonces la máquina virtual Java genera OutOfMemoryError.

Úselo para -Xssestablecer el espacio de pila máximo para subprocesos.
inserte la descripción de la imagen aquí
Los marcos de pila contenidos en subprocesos diferentes no pueden tener referencias mutuas, es decir, es imposible aplicar el marco de pila de otro subproceso en un marco de pila.
Si el método actual llama a otros métodos, cuando el método regresa, el marco de pila actual devolverá el resultado de la ejecución de este método al marco de pila anterior y, luego, la máquina virtual descarta el marco de pila actual, convirtiendo el marco de pila anterior en el marco de pila actual.
Hay dos formas de devolver una función Java: una es una devolución de función normal, como usar la instrucción de devolución; la otra es lanzar una excepción (aquí se lanza una excepción real. Si la excepción se detecta y procesa, es una devolución de función normal). De cualquier manera, el marco de la pila aparecerá.

[Estructura interna del marco de pila]
1 Tabla de variables locales, Variables locales
La tabla de variables locales también se denomina matriz de variables locales o tabla de variables locales.

Se define como una matriz numérica, utilizada principalmente para almacenar parámetros de métodos y variables locales definidas en el cuerpo del método (incluidos tipos de datos básicos, tipos de referencia y tipos de direcciones de retorno, etc.).

La tabla de variables locales es privada para subprocesos, por lo que no hay ningún problema de seguridad de datos.

La capacidad requerida de la tabla de variables locales se determina en el momento de la compilación y se almacena en el elemento de datos de variables locales máximas del atributo Código del método. El tamaño de la tabla de variables locales no cambia durante la ejecución del método.

Para una función, cuantos más parámetros y variables locales tenga, haciendo que la tabla de variables locales se expanda, mayor será su marco de pila.

Las variables en la tabla de variables locales solo son válidas en la llamada al método actual. Cuando se ejecuta el método, la máquina virtual completa el proceso de transferencia del valor del parámetro a la lista de variables de parámetros utilizando la tabla de variables locales. Cuando finaliza la llamada al método, a medida que se destruye el marco de la pila del método, también se destruirá la tabla de variables locales.

La unidad de almacenamiento más básica de la tabla de variables locales es Slot (ranura de variable). En la tabla de variables locales, los tipos dentro de los 32 bits solo ocupan una ranura (incluido el tipo returnAddress), y los tipos de 64 bits (largo y doble) necesitan ocupar dos ranuras. byte, short y char se convertirán a int antes del almacenamiento; el tipo booleano también se convertirá a int, 0 significa falso, distinto de cero significa verdadero.

Si necesita acceder a un valor de variable local de 64 bits (largo o doble) en la tabla de variables locales, solo necesita usar el índice anterior (64 bits ocupa dos ranuras).

Si el marco actual se crea mediante un método de construcción o un método de instancia, la referencia this del objeto se colocará en la ranura con índice 0, y el resto de los parámetros seguirán estando ordenados en el orden de la lista de parámetros.

Las ranuras se pueden reutilizar.

public void test4() {
    
    
	int a = 0;
	{
    
    
		int b = 0;
		b = a + 1;
	}
	// 局部变量表的长度就是3
	// 变量c使用之前已经销毁的变量b的slot
	int c = a + 1;
}

index  Name
2     b 
0     this
1     a
2     c

[Comparación entre variables estáticas y variables locales]
Clasificación de variables
Según tipos de datos: ① Tipos de datos básicos ② Tipos de datos de referencia
Según la posición declarada en la clase: ① Variables miembro: han pasado por el proceso de inicialización predeterminado antes de usar
<1> Variables de clase: En la fase de preparación del enlace, asigna valores iniciales predeterminados a las variables de clase; en la fase inicial: ejecuta sentencias en bloques de código estático y asigna valores.
<2> Variable de instancia: con la creación del objeto, el espacio de variable de instancia se asignará en el espacio de almacenamiento dinámico y se asignará de manera predeterminada.
② Variables locales: deben asignarse explícitamente antes de su uso. De lo contrario, la compilación falla.

public void test5Temp() {
    
    
	int num;
	// 直接编译报错:Variable 'num' might not have been initialized.
	System.out.println(num);
}

En el marco de la pila, la parte más estrechamente relacionada con el ajuste del rendimiento es la tabla de variables locales. Cuando se ejecuta el método, la máquina virtual utiliza la tabla de variables locales para completar la transferencia del método.
Las variables en la tabla de variables locales son los nodos raíz de la recolección de basura importante, siempre que los objetos a los que se hace referencia directa o indirectamente en la tabla de variables locales no se reciclen.

2 Pila de operandos, pila de operandos o pila de expresiones

La pila de operandos, durante la ejecución del método, escribe o extrae datos de la pila de acuerdo con las instrucciones del código de bytes.
Por ejemplo

[8]                                                                        [23]
[15]           -----------add 操作执行之后--------->                         [其他元素]
[其他元素]

Si el método llamado tiene un valor de retorno, su valor de retorno se insertará en la pila de operandos del marco de pila actual, y se actualizará la dirección de desplazamiento de la siguiente instrucción que se ejecutará en el registro pc.
Los tipos de datos de los elementos en la pila de operandos deben coincidir exactamente con la secuencia de instrucciones de código de bytes. El compilador verifica esto durante la compilación y nuevamente durante la fase de análisis de flujo de datos de la fase de verificación de clases del proceso de carga de clases.
El intérprete de la máquina virtual Java es un motor de ejecución basado en pilas, donde la pila es la pila de operandos.

La pila de operandos se utiliza principalmente para guardar los resultados intermedios del proceso de cálculo y, al mismo tiempo, como un espacio de almacenamiento temporal para variables durante el proceso de cálculo. La pila de operandos es un área de trabajo del motor de ejecución. Cuando un método comienza a ejecutarse, se crea un nuevo marco de pila y la pila de operandos también está vacía . La profundidad de la pila de operandos debe determinarse durante la compilación y almacenarse en max_stack del atributo Code. El tipo de 32 bits ocupa una profundidad de pila y el tipo de 64 bits ocupa dos. No se puede acceder a la pila de operandos a través de la indexación de matrices, y el acceso a los datos solo se puede completar a través de las operaciones de inserción y extracción de la pila.

[Tecnología de caché superior de pila]
Las instrucciones de dirección cero utilizadas por la máquina virtual basada en la arquitectura de pila son más compactas, pero cuando se completa una operación, se deben usar más instrucciones push y pop, lo que significa que se requerirán más tiempos de envío de instrucciones y lectura y escritura de memoria.

Dado que los operandos se almacenan en la memoria, las lecturas y escrituras frecuentes en la memoria afectarán inevitablemente la velocidad de ejecución. Para resolver este problema, los diseñadores de HotSpot JVM propusieron la tecnología Top-of-Stack Cashing (ToS, Top-of-Stack Cashing), que almacena en caché todos los elementos superiores de la pila en los registros de la CPU física, reduciendo así el número de lecturas y escrituras en la memoria y mejorando la eficiencia de ejecución del motor de ejecución.

3 Vinculación dinámica, vinculación dinámica o referencia de método al conjunto de constantes de tiempo de ejecución
Cada marco de pila contiene una referencia al método en el conjunto de constantes de tiempo de ejecución al que pertenece el marco. El propósito de incluir esta aplicación es apoyar el método actual para lograr la vinculación dinámica. Por ejemplo: invocar instrucción dinámica

Cuando un archivo fuente de Java se compila en un archivo de código de bytes, todas las referencias de métodos y variables se almacenan como referencias simbólicas (referencia simbólica) en el conjunto de constantes del archivo de clase. Por ejemplo: cuando se describe que un método llama a otro método, se representa mediante una referencia simbólica que apunta al método en el conjunto de constantes, entonces el rol del enlace dinámico es convertir estas referencias simbólicas en referencias directas al método que llama.

¿Por qué necesitas una piscina constante?
La función del conjunto de constantes es proporcionar algunos símbolos y constantes para facilitar la identificación de instrucciones.

[Llamada de método]
Enlace estático: cuando se carga un archivo de código de bytes en la JVM, si el método de destino llamado se conoce en tiempo de compilación y permanece sin cambios en tiempo de ejecución. En este caso, el proceso de convertir la referencia simbólica del método de llamada en una referencia directa se denomina enlace estático.
Vinculación dinámica: si el método llamado no se puede determinar en tiempo de compilación, en otras palabras, la referencia simbólica del método de llamada solo se puede convertir en una referencia directa en el tiempo de ejecución del programa. Este tipo de proceso de conversión de referencia es dinámico, por lo que también se denomina vinculación dinámica.

Enlace: el enlace es un proceso en el que un campo, método o clase se reemplaza por una referencia directa en una referencia simbólica, y este proceso ocurre solo una vez.
Enlace temprano: enlace estático, el método de destino se conoce en el momento de la compilación y permanece sin cambios durante el tiempo de ejecución, y se puede vincular directamente de forma estática.
Enlace tardío: enlace dinámico.

El lenguaje C orientado a procesos solo tiene enlace temprano. Un lenguaje orientado a objetos con polimorfismo y dos métodos de enlace: enlace temprano y enlace tardío.

Método no virtual: la versión de llamada se determina en tiempo de compilación y es inmutable en tiempo de ejecución. Esto se llama un método no virtual.
Un método no virtual es un método virtual.

Instrucción de llamada ordinaria
invocar estática: llama a un método estático y la única versión del método se determina en la etapa de análisis.
invocar especial: invoca métodos, métodos privados y principales, y la única versión del método se determina en la fase de análisis.
invoquevirtual: llama a todos los métodos virtuales, y también llama a los métodos modificados finales, pero los métodos modificados finales no son métodos virtuales
invoqueinterfaz: llama a los métodos de interfaz.
Invocación de instrucción dinámica de llamada dinámica
: analice dinámicamente el método que debe llamarse y luego ejecútelo.

Las primeras cuatro instrucciones se solidifican dentro de la máquina virtual, y la ejecución de la llamada al método no puede ser intervenida por humanos en absoluto. La instrucción invocación dinámica es compatible para determinar la versión del método.

Lenguaje tipificado estáticamente: el tipo se verifica en tiempo de compilación. Determinar el tipo de información de la propia variable.
Lenguajes tipificados dinámicamente: la comprobación de tipos se produce en tiempo de ejecución. Determine la información de tipo del valor de la variable, y la variable no tiene información de tipo.

[La esencia de la anulación de métodos en lenguaje Java]
(1) Encuentre el tipo real del objeto ejecutado por el primer elemento en la parte superior de la pila de operandos, denotado como C (2)
Si se encuentra un método que coincide con la descripción en la constante y el nombre simple en el tipo C, luego verifique la autoridad de acceso, si pasa, devuelva la referencia directa de este método y finaliza el proceso de búsqueda; Si no se encuentra un método adecuado java.lang.IllegalAccessError
,
arrojejava.lang.AbstractMethodError

java.lang.IllegalAccessErrorDescripción: el programa intenta acceder o modificar una propiedad, o el programa intenta llamar a un método, pero el programa no tiene acceso a esta propiedad o método. Generalmente, esta situación provocará una excepción del compilador. Si este error ocurre en tiempo de ejecución, se ha producido un cambio incompatible en una clase.

Para mejorar el rendimiento, la JVM crea una tabla de métodos virtuales (virtual method table) en el área de métodos de la clase. Use tablas indexadas en lugar de búsquedas. Cada clase tiene una tabla de método virtual, que almacena la entrada real de cada método. La tabla de métodos virtuales se creará e inicializará durante la fase de vinculación (fase de resolución) del proceso de carga de la clase.Después de preparar el valor inicial de la variable de la clase, la JVM también inicializará la tabla de métodos de la clase.

4 La dirección de retorno del método, Dirección de retorno, o la definición de salida normal y anormal del método
Almacenar el valor del registro de PC llamando al método

La salida normal guarda el valor del registro de PC, y la salida anormal no lo guarda. La dirección de retorno de la salida anómala debe determinarse de acuerdo con la tabla de excepciones , y esta parte de la información no se guardará en el marco de la pila.

ireturn: el valor de retorno es booleano, byte, char, short o int
lreturn: long
freturn: float
dreturn: double
areturn: tipo de referencia
return: sin valor de retorno, método void, método de construcción, bloque de código estático, etc.

5 Información adicional
Por ejemplo, información que proporciona soporte para la depuración de programas.

3.4.1 Varias preguntas de la entrevista

[Desbordamiento de pila]
StackOverFlowError

inserte la descripción de la imagen aquí

3.5 montones

La especificación de la máquina virtual de Java estipula que el montón puede estar en un espacio de memoria físicamente discontinuo , pero debería ser lógicamente continuo .

El búfer privado de subprocesos (Subproceso de asignación local de búfer, TLAB) se puede dividir especialmente en el espacio de almacenamiento dinámico.

inserte la descripción de la imagen aquí
Java 8 y posteriores se dividen lógicamente en tres partes: nueva generación + generación anterior + metaespacio (área de métodos).
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

-Xms: el tamaño inicial del montón (generación joven + generación anterior), ms es el inicio de la memoria
-Xmx: el tamaño máximo del montón
De forma predeterminada, el tamaño inicial del montón es la memoria física/64, la memoria máxima es la memoria física/4;

Ver los parámetros establecidos:
Método 1: jps / jstat -gc ID de proceso
Método 2: -XX:+PrintGcDetails

Cuando Eden esté lleno, se activará YGC (también conocido como MinorGC) y se reciclarán los objetos en el área de Eden y el área de Survivor.

Las reglas de funcionamiento del área de supervivientes s0 y s1: hay un intercambio después de la copia, y el vacío es el área.
inserte la descripción de la imagen aquí

Recolección de basura: recolectada con frecuencia en el área joven, raramente recolectada en el área antigua y casi nunca recolectada en el espacio de generación permanente/meta.

3.5.1 GC menor, GC mayor y GC completo

El GC se divide en recolección parcial (GC parcial) y recolección total (GC completo) según el área de recuperación.
Parte de la colección se divide en: (1) Recolección de nueva generación: Minor GC / Young GC, solo la recolección de basura de la nueva generación. Condición de activación: cuando el área de Eden está llena. Minor GC causará STW, suspenderá los subprocesos de otros usuarios y esperará a que finalice la recolección de elementos no utilizados antes de que los subprocesos del usuario se reanuden. (2) Recolección de generación anterior: Major GC / Old GC, solo recolección de basura en la generación anterior. Actualmente, solo el CMS GC tiene el comportamiento de recolectar por separado la generación anterior. Tenga en cuenta que Major GC y Full GC a menudo se usan de forma confusa, y es necesario distinguir específicamente si se trata de reciclaje de generación anterior o reciclaje general. Condición de activación: cuando no hay suficiente espacio en la generación anterior, intentará activar primero el gc menor. Si aún no hay espacio suficiente después de un gc menor, se activará un gc mayor. (3) Recogida mixta: GC mixta, que recoge toda la nueva generación y parte de la antigua. Actualmente, solo el G1 GC exhibe este comportamiento.

Colección de almacenamiento dinámico completo: GC completo, recopila todo el área de método y almacenamiento dinámico de Java.
La ejecución de Full GC es la siguiente: (1) Al llamar System.gc(), el sistema recomienda ejecutar Full GC, pero no necesariamente se ejecuta. (2) No hay suficiente espacio en la generación anterior. (3) No hay suficiente espacio en el área de métodos. (4) Después de Minor GC, el tamaño promedio de la generación anterior es mayor que la memoria disponible de la generación anterior. (5) Al copiar en el área de destino, si el tamaño del objeto es mayor que la memoria disponible en el área de destino, el objeto se transfiere a la generación anterior y la memoria disponible en la generación anterior es más pequeña que el tamaño del objeto.

3.5.2 Razones para utilizar el pensamiento generacional

Tampoco es posible la generación, la única razón para la generación es la optimización del rendimiento del GC .

3.5.3 Estrategia de asignación de memoria

  • Asignación prioritaria a Eden
  • Los objetos grandes se asignan directamente a la generación anterior.
    El programa debe tratar de evitar demasiados objetos grandes.
  • Los objetos de larga vida se asignan a la generación anterior.
  • Evaluación dinámica de la edad del objeto
    Si la suma del tamaño de todos los objetos de la misma edad en el área Superviviente es mayor que la mitad del espacio Superviviente, los objetos cuya edad sea mayor o igual a la nueva edad pueden ingresar directamente a la edad antigua sin esperar la edad requerida en MaxTenuringTreshold.
  • Garantía de asignación de espacio
    -XX:HandlePromotionFailure

3.5.4 TLAB

Divida el área de Eden y asigne un área de caché privada para cada subproceso.
La JVM usa TLAB como la primera opción para la asignación de memoria.
Por defecto, el espacio TLAB es muy pequeño, solo el 1% del espacio Eden.

3.5.5 ¿Es el montón la única opción para asignar almacenamiento de objetos?

【Asignación en la pila】

Si después del análisis de escape, se encuentra que un objeto no escapa al método, entonces puede optimizarse para ser asignado en la pila.

[Sincronización omitida]

El análisis de escape realizará la optimización de eliminación de bloqueo.

public void f() {
    
    
	Object hollis = new Object();
	synchronized (hollis) {
    
    
		System.out.println(hollis);
	}
}

=====优化后=======
public void f() {
    
    
	Object hollis = new Object();
	System.out.println(hollis);
}

【Reemplazo escalar】

Un escalar son datos que no se pueden descomponer en datos más pequeños. Los tipos de datos primitivos en Java son escalares. Por el contrario, los datos que se pueden descomponer se denominan agregación. Los objetos en Java son agregados.
En la etapa JIT, si el análisis de escape encuentra que el mundo exterior no accederá a un objeto, luego de la optimización JIT, el objeto se desarmará en varios escalares para su reemplazo. Esta es la sustitución escalar.

[Área de metadatos]
El análisis de escape no está maduro. El análisis de escape en sí requiere un gran consumo de rendimiento y no hay garantía de que el análisis de escape traiga beneficios.

La caché de cadenas internas y las variables estáticas no se asignan en el área de metadatos, sino que se asignan directamente en el montón.

Por lo tanto, los objetos se asignan básicamente en el montón.

3.5.5 Ajuste del montón

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

4 Creación de instancias de objetos, diseño de memoria y posicionamiento de acceso

4.1 Ejemplificación de objetos

4.1.1 Cómo se crean los objetos

  • nueva
    variante 1: método estático de XXX, como getInstance del modo singleton
    variante 2: método estático de xxxBuilder/xxxFactory
  • Reflection
    Class.newInstance: solo se pueden llamar constructores con parámetros vacíos, y los permisos también deben ser públicos.
    Constructor.newInstance: se pueden llamar constructores sin parámetros o parámetros, y no se requieren permisos.
  • Para usar la clonación,
    debe implementar el método de clonación
  • usar deserialización
  • Biblioteca tripartita Objenesis

4.1.2 Pasos de la creación de objetos

[1 Determinar si la clase correspondiente está cargada, conectada e inicializada]
[2 Asignar memoria para el objeto]
Si la memoria es normal, utilice la colisión de punteros.
Si la memoria es irregular, asigne memoria a través de la lista libre.
[3 Manejo de problemas de seguridad de concurrencia]
El reintento de falla de CAS, el bloqueo de área, etc. se utilizan para garantizar la atomicidad de las actualizaciones.
TLAB
[4 Inicializar el espacio asignado]
Establezca valores predeterminados para todos los atributos para garantizar que la instancia del objeto se pueda usar directamente cuando no se asigna ningún valor
[5 Establezca el encabezado de objeto del objeto]

[6 Inicialización explícita]
init y clinit

4.2 Diseño de memoria de objetos

[cabeza de objeto]


  • Código hash de metadatos de tiempo de ejecución (Mark Word)
    Edad generacional de GC
    Indicador de estado de bloqueo
    Bloqueo retenido por subproceso Marca de tiempo de sesgo
    de identificación de subproceso de sesgo
  • El puntero de tipo
    apunta al objeto Clase

Si es una matriz, también se registrará la longitud de la matriz.
[Instancia de datos]
La variable definida en la clase principal está antes de la variable de la subclase.
Los campos del mismo ancho siempre se asignan juntos.
Si el parámetro CompactFields es verdadero (el valor predeterminado es verdadero): la variable estrecha de la subclase se puede insertar en el espacio de la variable de la clase principal.
【Relleno de alineación】

4.3 Ubicación de acceso al objeto

  • Beneficios de acceso a la manija
    : cuando se mueve el objeto, solo necesita modificar el puntero de la manija.
    Desventajas: necesita abrir memoria para el grupo de identificadores, referencia de dos niveles y la eficiencia de acceso es relativamente baja.
  • Punteros directos
    HotSpot utiliza punteros directos.
    Ventajas:
    Desventajas:

5 memoria directa

IO NIO (E/S nuevo/E/S sin bloqueo)
byte[] / carácter[] Buffer
Arroyo Canal
// 直接分配本地内存空间
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 1024)

La memoria directa no forma parte del área de datos de tiempo de ejecución ni es un área de memoria tal como se define en la especificación de máquina virtual de Java. Está fuera del montón de Java y se aplica directamente al sistema por espacio de memoria.
Con origen en NIO, la memoria nativa se manipula a través de DirectByteBuffer almacenada en el montón.
En general, la velocidad de acceso a la memoria directa es mejor que la del montón de Java, y el rendimiento de lectura y escritura es alto.
En el caso de OOM,
puede MaxDirectMemorySizeestablecer el tamaño de la memoria directa, que es consistente con el valor del parámetro de -Xmx por defecto.

6 Motor de ejecución

6.1 Razones para la coexistencia de intérprete y compilador JIT

JIT: Intérprete rápido
: después de que se inicia el programa, el intérprete puede surtir efecto de inmediato, ahorrando tiempo de compilación y ejecutándolo de inmediato.

[Contador de llamadas de método]
Tiempos de llamada de método de estadísticas. El umbral predeterminado es 1500 veces en modo Cliente y 10000 veces en modo Servidor. Los umbrales se pueden -XX:CompileThresholdestablecer a través de .

El contador cuenta la frecuencia de ejecución relativa (el número de veces que se llama a un método durante un período de tiempo). Cuando se excede un cierto límite de tiempo y el número de llamadas a métodos aún no alcanza el umbral, el conteo se reducirá a la mitad. Este proceso se llama Counter Decay. Este período de tiempo se denomina período de vida media (Counter Half life time).

-XX:-UseCounterDecay: Apagar el decaimiento por calor
-XX:-CounterHalfLifeTime: Periodo de vida media, unidad s.
[Contador final]
Cuenta el número de ejecuciones de código en el cuerpo del bucle.
Las instrucciones que saltan hacia atrás cuando encuentran un flujo de control en el código de bytes se denominan "Back Edge".

-Xint: usa completamente el intérprete para ejecutar
-Xcomp: usa completamente el compilador justo a tiempo para ejecutar el programa. Si hay un problema con el compilador justo a tiempo, el intérprete intervendrá para ejecutarlo.
-Xmixed: Usa el modo mixto de intérprete + compilador justo a tiempo para ejecutar juntos.

Hay dos compiladores JIT en HotSpot VM, a saber, Client Compiler y Server Compiler. Conocidos como compiladores C1 y C2.
-cliente: compilador C1. El compilador C1 realiza una optimización simple y confiable del código de bytes en una fracción del tiempo. Tiene una velocidad de compilación más rápida.
Método en línea: compile el código de función al que se hace referencia en el punto de referencia, reduzca la generación de marcos de pila y reduzca el proceso de paso y salto de parámetros.
Desvirtualización: Integración de la única clase de implementación.
Eliminación de Honor: Dobla algún código que no se ejecutará durante el tiempo de ejecución.
-servidor: compilador C2. El compilador C2 realiza optimizaciones agresivas más largas. El código optimizado es más eficiente.
Reemplazo escalar
Asignación de pila
Eliminación de sincronización.

Estrategia de compilación en capas: la compilación C1 se puede activar cuando el programa se interpreta y ejecuta, y se realiza una optimización simple al compilar el código de bytes en el código de la máquina. Si se agrega la supervisión del rendimiento, el compilador C2 realizará una optimización agresiva en función de la información de supervisión del rendimiento.
Después de java7, siempre que esté en modo servidor, la estrategia de compilación jerárquica estará habilitada de forma predeterminada.

6.2 Compilador AOT

Compilador estático adelantado al tiempo, compilador adelantado al tiempo. Un compilador AOT convierte el código de bytes en código de máquina antes de que se ejecute el programa.

Mesa de 7 cuerdas

La estructura de datos de String en java8 es char[], y cambió a byte[] en jdk 9.
Motivo cambiado: según las estadísticas, la mayoría de las cadenas son letras latinas, y es una pérdida de espacio usar el almacenamiento de matriz char.
El indicador de codificación de cadenas (encoding-flag) se ha agregado a String de Java 9.

Las cadenas con el mismo contenido no se almacenarán en el grupo de constantes de cadenas.

String Pool es un HashTable de tamaño fijo con una longitud predeterminada de 1009 (el valor predeterminado en jdk 6). En jdk 7, la longitud predeterminada de StringTable es 60013 (la longitud se puede configurar, pero no hay requisitos). A partir de JDK 8, 1009 es el valor mínimo que se puede configurar.

7.1 Operación de concatenación de cadenas

  1. Las constantes y los resultados de empalme de constantes están en el grupo de constantes, el principio es la optimización en tiempo de compilación.
public void test1() {
    
    
	String s1 = "a" + "b" + "c";
	String s2 = "abc";

	System.out.println(s1 == s2); // true
	System.out.println(s1.equals("s2")); // true 
}
  1. Las constantes con el mismo contenido no existirán en el grupo de constantes
  2. Siempre que uno sea una variable, el resultado está en el montón. El principio del empalme de variables es StringBuilder
public void test2() {
    
    
	String s1 = "javaEE";
	String s2 = "hadoop";
	String s3 = "javaEEhadoop";
	String s4 = "javaEE" + "hadoop";
	String s5 = s1 + "hadoop";
	String s6 = "javaEE" + s2;
	/**
	* 执行细节:
	* ① builder = new StringBuilder();
	* ② builder.append(s1)
	* ③ builder.append(s2)
	* ④ builder.toString(); 约等于new String
	*  
	* jdk 5.0和之后使用的是StringBuilder,5.0之前使用的StringBuffer
	**/
	String s7 = s1 + s2;

	System.out.println(s3 == s4); // true
	System.out.println(s3 == s5); // false
	System.out.println(s3 == s6); // false
	System.out.println(s3 == s7); // false
	System.out.println(s5 == s6); // false	
	System.out.println(s5 == s7); // false
	System.out.println(s6 == s7); // false	
	
	String s8 = s6.intern();
	System.out.println(s3 == s8); // true
}
public void test4() {
    
    
	final String s1 = "a";
	final String s2 = "b";
	String s3 = "ab";
	String s4 = s1 + s2; // 因为有final修饰,这里是“常量”
	System.out.println(s3 == s4); // true
}
  1. Si el resultado del empalme llama al método interno, colocará activamente la cadena que no está en el grupo constante en el grupo y devolverá la dirección de este objeto.

7.2 método interno

【¿Cuántos objetos creará new String("ab")?
Dos, el nuevo, el "ab" en el conjunto de cadenas constantes es el otro.

Use jclasslib para ver el archivo de código de bytes compilado para saber.

[String str = new String("a") + new String("b") ¿Cuántos objetos se crearán?
Objeto 1: nuevo
objeto StringBuilder 2: nuevo objeto String("a")
3: objeto "a" en el grupo de constantes
4: nuevo objeto String("b")
5: objeto "b" en el grupo de constantes
6: toString() en StringBuilder creará un nuevo String("ab"), pero no hay "ab" en el grupo de constantes de cadenas

        // new 出来的对象
        String s = new String("1");
        // 没有作用,常量池中已经有“1”了。
        s.intern();
        // 指向常量池中的string
        String s2 = "1";
        // jdk 6/7/8以及之后,都是false
        System.out.println(s == s2);

        // s3 变量的地址就是new String("11")
        String s3 = new String("1") + new String("1");
        // "11"加入到了常量池中
        // jdk 6: 创建了一个新的对象“11”,放到常量池中
        // jdk 7/8和以后:常量池中记录了堆里面的“11”的地址,没有创建新的。
        s3.intern();
        // s4就是常量池中“11”的地址
        String s4 = "11";
        System.out.println(s3 == s4);
		// s3 变量的地址就是new String("11")
        String s3 = new String("1") + new String("1");
        // 字符串常量池中创建新的。 
        String s4 = "11";
        String s5 = s3.intern();
        System.out.println(s3 == s4); // false
        System.out.println(s5 == s4); // true
 		// s = new String("ab")
        String s = new String("a") + new String("b");
        // jdk 6 : s2 != s
        // jdk 17 : s2 == s.
        String s2 = s.intern();
        System.out.println(s2 == "ab"); // jdk 6: true.    jdk 17: true
        System.out.println(s == "ab");  // jdk 6: false.   jdk 17: true
        String x = "ab";
        // s = new String("ab")
        String s = new String("a") + new String("b");
        String s2 = s.intern();
        System.out.println(s2 == "ab"); // true
        System.out.println(s == "ab"); // false
        String s1 = new String("a") + new String("b");
        // 常量池中的字串引用指向s1
        s1.intern();
        // 常量池中的字串
        String s2 = "ab";
        System.out.println(s1 == s2); // open jdk 17:true
        // s1是new出来的
        String s1 = new String("ab");
        // 没有作用,常量池中已经有ab了。
        s1.intern();
        String s2 = "ab";
        System.out.println(s1 == s2); // open jdk 17:false

7.3 Operación de deduplicación de cadenas de G1

No está habilitado por defecto.
Deduplicar la cadena en el montón.

Supongo que te gusta

Origin blog.csdn.net/kaikai_sk/article/details/131744449
Recomendado
Clasificación