Shang Silicon Valley-Song Hongkang-JVM Notas completas-JVM Parte 1_Memoria y recolección de basura

prefacio

1. arquitectura jvm y java

1. Introducción a Java y JVM

Clasificación de popularidad del lenguaje TIOBE
https://www.tiobe.com/tiobe-index/
No existe el mejor lenguaje de programación del mundo, solo el lenguaje de programación más adecuado para escenarios de aplicación específicos.

  • java: un lenguaje multiplataforma
    inserte la descripción de la imagen aquí
  • JVM: una plataforma multilenguaje
    inserte la descripción de la imagen aquí
  • Con el lanzamiento oficial de Java 7, los diseñadores de la máquina virtual Java básicamente implementaron programas escritos en lenguajes que no son Java en la plataforma de la máquina virtual Java a través de la especificación JSR-292.
  • A la máquina virtual Java no le importa en qué lenguaje de programación está escrito el programa que se ejecuta en su interior. Solo le importa el archivo de "código de bytes". Es decir, la máquina virtual Java tiene independencia de lenguaje y no está simplemente relacionada con Java. Lenguaje "enlace de por vida", siempre que los resultados de compilación de otros lenguajes de programación cumplan e incluyan el conjunto de instrucciones internas, la tabla de símbolos y otra información auxiliar de la máquina virtual Java, es un archivo de código de bytes válido y puede ser reconocido por el virtual máquina y cargar y ejecutar.
  • código de bytes
  • El código de bytes de Java del que solemos hablar se refiere al código de bytes compilado en el lenguaje Java. Para ser precisos, cualquier formato de código de bytes que se puede ejecutar en la plataforma jvm es el mismo. Por lo tanto, debe denominarse colectivamente como: código de bytes jvm.
  • Diferentes compiladores pueden compilar el mismo archivo de bytecode, y el archivo de bytecode también puede ejecutarse en diferentes jvms.
  • La máquina virtual Java no está necesariamente relacionada con el lenguaje Java. Solo está asociada con un formato de archivo binario específico: formato de archivo de clase. El archivo de clase contiene el conjunto de instrucciones de la máquina virtual Java (o llamado código de bytes, Bytecodes) y la tabla de símbolos, y alguna otra información auxiliar.
  • Programación mixta multilenguaje
  • La programación mixta multilingüe en la plataforma Java se está convirtiendo en la corriente principal, y la resolución de problemas en dominios específicos a través de lenguajes específicos de dominio es una dirección para el desarrollo de software actual para cumplir con los requisitos de proyectos cada vez más complejos.
  • Imagínese, en un proyecto, el procesamiento paralelo está escrito en lenguaje Clojure, la capa de presentación usa JRuby/Rails, la capa intermedia usa Java, cada capa de aplicación usará diferentes lenguajes de programación para completarse, y la interfaz es para cada uno de los desarrolladores. de la capa son transparentes No hay dificultad en la interacción entre varios lenguajes, tan conveniente como usar la API nativa de su propio idioma, porque al final todos se ejecutan en una máquina virtual.
  • Para lenguajes distintos a Java que se ejecutan en la máquina virtual Java, el soporte desde el nivel del sistema y la capa inferior está aumentando rápidamente Una serie de proyectos y mejoras funcionales centradas en JSR-292 (como el proyecto DaVinci Machine, el motor Nashorn , instrucciones InvokeDynamic, paquete java.lang.invoke, etc.), para promover el desarrollo de la máquina virtual Java de "máquina virtual de lenguaje Java" a "máquina virtual multilingüe".

2. Principales acontecimientos en el desarrollo de Java

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

  • Abrir JDK y Oracle JDK
    Por favor agregue una descripción de la imagen

Antes de JDK11, habrá algunas funciones de código cerrado en OracleJDK que no están en OpenJDK, pero en JDK11, podemos pensar que los códigos OpenJDK y OracleJDK son esencialmente idénticos.

3. Máquina virtual y máquina virtual Java

  • máquina virtual
  • La llamada máquina virtual (Virtual Machine) es una computadora virtual, que es una pieza de software que se utiliza para ejecutar una serie de instrucciones de una computadora virtual. En general, las máquinas virtuales se pueden dividir en máquinas virtuales de sistema y máquinas virtuales de programa.
  • Las conocidas Visual Box y VMware pertenecen al sistema de máquinas virtuales, que son simulaciones completamente de computadoras físicas y brindan una plataforma de software que puede ejecutar un sistema operativo completo.
  • El representante típico de la máquina virtual de programa es la máquina virtual de Java, que está especialmente diseñada para ejecutar un solo programa de computadora.Las instrucciones ejecutadas en la máquina virtual de Java se denominan instrucciones de código de bytes de Java.
  • Ya sea una máquina virtual de sistema o una máquina virtual de programa, el software que se ejecuta en ella está limitado a los recursos proporcionados por la máquina virtual.
  • Máquina virtual de Java
  • La máquina virtual Java es una computadora virtual que ejecuta el código de bytes de Java. Tiene un mecanismo operativo independiente y es posible que el código de bytes de Java que ejecuta no esté compilado a partir del lenguaje Java.
  • Varios lenguajes de la plataforma JVM pueden compartir el tipo multiplataforma traído por la máquina virtual Java, excelentes recolectores de basura y compiladores científicos justo a tiempo.
  • El núcleo de la tecnología JAVA es la Máquina Virtual Java (JVM, Máquina Virtual Java), porque todos los programas Java se ejecutan dentro de la Máquina Virtual Java.

- Función:

  • La máquina virtual JAVA es el entorno operativo del bytecode binario, se encarga de cargar el bytecode en su interior, interpretarlo/compilarlo en instrucciones máquina en la plataforma correspondiente para su ejecución, cada instrucción Java tiene una definición detallada en la máquina virtual Java especificación, por ejemplo, cómo obtener operandos, cómo procesar los operandos y dónde colocar los resultados del procesamiento.

- Características:

  1. Compilar una vez, ejecutar en todas partes 2. Gestión automática de memoria 3. Función de recolección de basura automática
  • La ubicación de la JVM
    Por favor agregue una descripción de la imagen

La JVM se ejecuta sobre el sistema operativo y no tiene interacción directa con el hardware.

4. La estructura general de la JVM

  • HotSpot VM es uno de los representantes de máquinas virtuales de alto rendimiento actualmente en el mercado.
  • Adopta una arquitectura donde coexisten un intérprete y un compilador justo a tiempo.
  • Hoy, el rendimiento operativo de los programas Java ha renacido y ha llegado al punto en que puede competir con los programas C/C++.

Hable acerca de su comprensión general de la JVM?

  • subsistema de carga de clases
  • Área de datos de tiempo de ejecución (aquí nos enfocamos en las áreas de pila, montón y método)
  • Motor de ejecución (el intérprete y el compilador JIT coexisten)

5. Proceso de ejecución de código Java

Por favor agregue una descripción de la imagen

6. Modelo de arquitectura de JVM

El flujo de instrucciones de entrada del compilador de Java es básicamente una arquitectura de conjunto de instrucciones basada en pila, y otra arquitectura de conjunto de instrucciones es una arquitectura de conjunto de instrucciones basada en registros. Específicamente: la diferencia entre las dos arquitecturas
:

  • Características basadas en arquitectura de pila:
  1. Más simple de diseñar e implementar, adecuado para sistemas con recursos limitados.
  2. Evitar el problema de la asignación de registros: se aplica la asignación de instrucciones de dirección cero.
  3. La mayoría de las instrucciones en el flujo de instrucciones son instrucciones de dirección cero y su ejecución depende de la pila de operaciones.El conjunto de instrucciones es más pequeño y el compilador es fácil de implementar.
  4. Sin necesidad de soporte de hardware, mejor portabilidad y mejor implementación multiplataforma.
  • Características de la arquitectura basada en registros:
  1. Una aplicación típica es el conjunto de instrucciones binarias x86, como la máquina virtual tradicional Android Davlik a nivel de PC.
  2. La arquitectura del conjunto de instrucciones depende completamente del hardware y tiene poca portabilidad.
  3. Excelente rendimiento y ejecución más eficiente.
  4. Se necesitan menos búsquedas de instrucciones para completar una operación.
  • En la mayoría de los casos, los conjuntos de instrucciones basados ​​en registros se basan en instrucciones de una dirección, instrucciones de dos direcciones e instrucciones de tres direcciones, y el segundo conjunto de instrucciones basado en la pila está dominado por instrucciones de dirección cero.

Resumir:

  • Debido al diseño multiplataforma, las instrucciones de Java se diseñan de acuerdo con la pila. Las diferentes plataformas tienen diferentes arquitecturas de CPU, por lo que no se pueden relacionar con las basadas en registros. Las ventajas son la multiplataforma, el pequeño conjunto de instrucciones y la fácil implementación del compilador La desventaja es que el rendimiento cae, para lograr la misma función requiere más instrucciones.
  • Hoy en día, aunque la plataforma integrada ya no es la plataforma operativa principal para los programas Java (hablando con precisión, el entorno host de HotSportVM ya no se limita a la plataforma integrada), ¿por qué no reemplazar la arquitectura con una arquitectura basada en registros?

7. Ciclo de vida de JVM

Inicie la máquina virtual:

  • El inicio de la máquina virtual Java se completa con la creación de una clase inicial (clase inicial) a través del cargador de clases de arranque, que se especifica mediante la implementación específica de la máquina virtual.

Ejecutando la máquina virtual:

  • Una máquina virtual Java en ejecución tiene una misión clara: ejecutar programas Java.
  • Se ejecuta cuando el programa comienza a ejecutarse y se detiene cuando finaliza el programa.
  • Cuando se ejecuta un programa llamado Java, lo que en realidad se está ejecutando es un proceso llamado máquina virtual Java.

Salida de la máquina virtual:

Hay varias situaciones de la siguiente manera:

  • El programa termina normalmente
  • El programa finaliza de manera anormal cuando se encuentra una excepción o un error durante la ejecución.
  • El proceso de la máquina virtual Java finalizó debido a un error en el sistema operativo.
  • Un subproceso llama al método de salida de la clase de tiempo de ejecución o de la clase de sistema, o al método de detención de la clase de tiempo de ejecución, y el administrador de seguridad de Java también ejecuta esta operación de salida o detención.
  • Además, la especificación JNI (Java Native InterFace) describe el uso de la API de invocación de JNI para cargar o descargar la máquina virtual Java y la salida de la máquina virtual Java.

8. Historial de desarrollo de JVM

  • Máquina virtual clásica de Sun
  • Ya en 1996, cuando se lanzó Java 1.0, Sun no pudo lanzar una máquina virtual Java llamada Sun Classic VM. También fue la primera máquina virtual Java comercial del mundo y se eliminó por completo en JDK 1.4.
  • Solo se proporciona un intérprete dentro de esta máquina virtual.
  • Si usa el compilador JIT, debe conectarse. Pero una vez que se usa el compilador JIT, el JIT se hará cargo del sistema de ejecución de la máquina virtual. El intérprete ya no funcionará. El intérprete y el compilador no pueden funcionar juntos.
  • Ahora, el punto de acceso tiene esta máquina virtual integrada.
  • Máquina virtual exacta
  • Para resolver el problema anterior de la máquina virtual, cuando jdk1.2, sun proporciona esta máquina virtual.
  • Gestión de memoria exacta: gestión de memoria precisa.
  • También se puede llamar Gestión de memoria no conservadora/precisa
  • La máquina virtual puede saber qué tipo de datos se encuentran en una ubicación determinada de la memoria.
  • Prototipo concreto de máquina virtual moderna de alto rendimiento
  1. detección de puntos de acceso
  2. Modo de trabajo mixto de compilador e intérprete
  • Solo se usa brevemente en la plataforma Solaris y sigue siendo una máquina virtual clásica en otras plataformas.
  1. El héroe estaba sin aliento y finalmente fue reemplazado por la máquina virtual Hotspot.
  • Máquina virtual HotSpot de Sun
  • Historia de HotSpot
  1. Inicialmente había una pequeña empresa llamada "Longview Technologies" involucrada con .
  2. En 1997, la empresa fue adquirida por Sun, y en 2009, Sun fue adquirida por Oracle.
  3. En JDK1.3, HotSpot VM se convierte en la máquina virtual predeterminada.
  • Actualmente HotSpot ocupa una posición absoluta en el mercado, dominando las artes marciales
  1. Ya sea JDK6, que todavía se usa ampliamente, o JDK8, que se usa más, la máquina virtual predeterminada es HotSpot.
  2. La máquina virtual predeterminada para Sun/Oracle JDK y OpenJDK.
  3. Por lo tanto, las máquinas virtuales predeterminadas que se presentan en este curso son todas HotSpot.
  • Desde el servidor, el escritorio hasta el móvil, hay aplicaciones integradas.
  1. Encuentre el código con el mayor valor de compilación a través del contador, active la compilación justo a tiempo o el reemplazo en la pila.
  2. El compilador y el intérprete trabajan juntos para lograr un equilibrio entre el tiempo de respuesta del programa optimizado y el rendimiento de ejecución óptimo.
  • JRockit de BEA
  • Concéntrese en las aplicaciones del lado del servidor
  1. Puede prestar menos atención a la velocidad de inicio del programa, por lo que JRockit no incluye una implementación de intérprete en su interior, y todos los códigos son compilados y ejecutados por un compilador justo a tiempo.
  • Numerosos puntos de referencia de la industria muestran que JRockit JVM es la JVM más rápida del mundo
  1. Con los productos JRockit, ya puede experimentar mejoras significativas en el rendimiento (generalmente más del 70 %) y reducciones en los costos de hardware (50 %).
  • Puntos fuertes: Cartera completa de soluciones de tiempo de ejecución de Java
  1. La solución de JRockit para aplicaciones sensibles a los retrasos, JRockit Real Time, proporciona un tiempo de respuesta de JVM en milisegundos o microsegundos, lo cual es adecuado para redes financieras, militares y de telecomunicaciones.
  2. Paquete de servicios MissionControl, que es un conjunto de herramientas para monitorear, administrar y analizar aplicaciones en entornos de producción con una sobrecarga muy baja.
  • En 2008, BEA fue adquirida por Oracle.
  • Oracle expresó el salario de integrar las dos ventajas de la máquina virtual, que se completa aproximadamente en JDK 8. La forma de integración es trasplantar las excelentes características de JRockit sobre la base de HotSpot.
  • ibm j9
  • Nombre completo: IBM Technology for Java Virtual Machine, denominada IT4J, código interno: J9.
  • El punto de mercado está cerca de HotSpot, del lado del servidor, aplicaciones de escritorio, máquinas virtuales integradas y otras máquinas virtuales multipropósito.
  • Ampliamente utilizado en varios productos Java de IBM
  • Actualmente, es uno de los tres servidores comerciales más influyentes y también es conocida como la máquina virtual Java más rápida del mundo.
  • Alrededor de 2017, IBM lanzó la máquina virtual J9 de código abierto, llamada Open J9, que fue administrada por la Fundación Eclipse y se convirtió en Eclipse OpenJ9.
  • Puntos de acceso KVM y CDC/CLDC
  • Las dos máquinas virtuales de Oracle en la línea de productos Java ME son: VM de implementación CDC/CLDC HotSpot>
  • KVM (Kilobyte) es uno de los primeros productos de CLDC-HT.
  • En la actualidad, la posición en el campo móvil es vergonzosa y el teléfono inteligente está dividido en dos partes por Android e IOS.
  • KVM es simple, liviano, altamente portátil y mantiene su propio mercado para dispositivos de gama baja
  1. controlador inteligente, sensor
  2. Teléfonos móviles para personas mayores, teléfonos básicos en áreas económicamente subdesarrolladas
  • El principio de todas las máquinas virtuales: compilar una vez, ejecutar en todas partes.
  • Azul VM
  • Las primeras tres "máquinas virtuales Java de alto rendimiento" se utilizan en plataformas de hardware de propósito general.
  • Aquí, Azul VM y BEA Liquid VM son máquinas virtuales dedicadas con enlace de plataforma de hardware específico con hardware y software
  • Un luchador en una máquina virtual Java de alto rendimiento.
  • Azul VM es una máquina virtual Java que Azul Systems ha realizado muchas mejoras sobre la base de Hotspot y se ejecuta en el sistema Vega propietario de Azul Systems.
  • Cada instancia de VM de Azul puede administrar recursos de hardware de al menos docenas de CPU y cientos de GB de memoria, y proporciona un recolector de basura que realiza un tiempo de GC controlable en un rango de memoria enorme y una programación de subprocesos optimizada por hardware propietario, etc. característica.
  • En 2010, Azul Systems comenzó a pasar del hardware al software y lanzó su propia Zing JVM, que puede proporcionar funciones cercanas a los sistemas Vega en la plataforma general X86.
  • máquina virtual líquida
  • Un luchador en una máquina virtual Java de alto rendimiento.
  • Desarrollado por BEA, se ejecuta directamente en su propio sistema Hypervisor.
  • Liquid VM Incluso el JRockit VE actual, Liquid VM no necesita el soporte del sistema operativo, o implementa las funciones necesarias de un sistema operativo propietario, como la programación de subprocesos, el sistema de archivos, el soporte de red, etc.
  • Con el final del desarrollo de la máquina virtual JRockit, el proyecto Liquid VM también se suspendió.
  • armonía apache
  • Apache también ha lanzado Apache Harmony, una plataforma de tiempo de ejecución de Java compatible con JDK1.5 y JDK1.6.
  • Es una JVM de código abierto desarrollada conjuntamente por IBM e Intel. Fue suprimida por el mismo OpenJDK de código abierto. Sun se negó resueltamente a permitir que Harmony obtuviera la certificación JCP y finalmente se retiró en 2011. IBM volvió a participar en OpenJDK.
  • Aunque no existe un uso comercial a gran escala de Apache Harmony, su código de la biblioteca de clases de Java se ha absorbido en el SDK de Android.
  • JVM de Microsoft
  • Microsoft desarrolló Microsoft JVM para admitir Java Applets en el navegador IE3.
  • Solo puede ejecutarse en la plataforma de Windows, pero en realidad era la máquina virtual de Java con el mejor rendimiento en Windows en ese momento.
  • En 1997, Sun demandó a Microsoft por infracción de marca registrada y cargos de competencia desleal, y gastó mucho dinero con Sun. Microsoft borró su VM en WinowsXP SP3. Ahora, el JDK instalado en Windows es HotSpot.
  • TaobaoJVM
  • Publicado por el equipo de AliJVM. Ali, la compañía más poderosa que usa Java en China, cubre computación en la nube, finanzas, logística, comercio electrónico y muchos otros campos, y necesita resolver problemas de alta concurrencia, alta disponibilidad y compuestos distribuidos. un gran número de productos de código abierto.
  • Basado en OpenJDK, desarrolló su propia versión personalizada AlibabaJDK, denominada AJDK, es el tiempo base de todo el sistema Java de Alibaba.
  • Basado en OpenJDK HotSpot VM, la primera máquina virtual Java de versión de servidor de alto rendimiento optimizada a nivel nacional, profundamente personalizada y de código abierto.
  1. La innovadora tecnología GCIH (GC invisible heap) se realiza fuera del montón, es decir, los objetos de Java con un ciclo de vida largo se mueven del montón al exterior del montón, y el GC no puede administrar los objetos de Java dentro del GCIH, lo que reduce la frecuencia de reciclaje. del GC a la vez Y el propósito de mejorar la eficiencia de recuperación del GC.
  2. Los objetos en GCIH también se pueden compartir entre múltiples procesos de máquinas virtuales Java.
  3. Use las instrucciones crc32 para implementar los intrínsecos de JVM para reducir la sobrecarga de llamadas de JNI.
  4. Herramienta de generación de perfiles de Java y función de asistente de diagnóstico del hardware de PMU.
  5. ZenGC para escenarios de big data.
  • La aplicación Taobao VM tiene un alto rendimiento en los productos Ali, y el hardware depende en gran medida de la CPU de Intel, que pierde compatibilidad pero mejora el rendimiento.
  1. Actualmente, se ha lanzado en Taobao y Tmall, reemplazando todas las versiones oficiales de Oracle JVM.

Subsistema de Carga Clase II

inserte la descripción de la imagen aquí

1. El papel del subsistema del cargador de clases

inserte la descripción de la imagen aquí

  • El subsistema del cargador de clases es responsable de cargar archivos de clase desde el sistema de archivos o el centro de red, y los archivos de clase tienen identificadores de archivo específicos al principio del archivo.
  • ClassLoader solo es responsable de cargar archivos de clase, y Execution Engine determina si puede ejecutarse.
    La información de clase cargada se almacena en un espacio de memoria llamado área de método. Además de la información de la clase, el área del método también almacena información del grupo de constantes en tiempo de ejecución, que también puede incluir literales de cadena y constantes numéricas (esta parte de la información constante es el mapa de memoria de la parte del grupo de constantes en el archivo de Clase)

2. Rol ClassLoader de carga de clase

inserte la descripción de la imagen aquí

  • El archivo de clase existe en el disco duro local, que puede entenderse como una plantilla dibujada por el diseñador en papel, y finalmente la plantilla se carga en la JVM cuando se ejecuta para instanciar n instancias idénticas basadas en este archivo.
  • El archivo de clase se carga en la JVM, conocida como plantilla de metadatos de ADN, y se coloca en el área de métodos.
  • En el archivo .class --> JVM --> eventualmente se convierte en una plantilla de metadatos, este proceso requiere una herramienta de transporte (Class Loader) para desempeñar el papel de un mensajero.

3. Proceso de carga de clases

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

3.1 Fase de carga

  • Obtenga el flujo de bytes binarios que define esta clase por su nombre completo.
  • Convierta el resultado de almacenamiento estático representado por este flujo de bytes en la estructura de datos de tiempo de ejecución del área de método.
  • Un objeto java.lang.Class que representa esta clase se genera en la memoria como una entrada de acceso para varios datos de esta clase en el área de método.
  • Después de que la clase cargue el archivo .class en el metaespacio, se creará un objeto java.lang.Class en el montón para encapsular la estructura de datos de la clase en el área del método. El objeto Clase se crea durante el proceso de carga de la clase, y cada clase corresponde a un objeto del tipo Clase.

Suplemento: La forma de cargar archivos .class

  • Cargar directamente desde el sistema local
  • Obtenido a través de la red, escenario típico: Web Applet
  • Lea del archivo zip y conviértase en la base de los formatos jar y war en el futuro
  • Generación de cálculo en tiempo de ejecución, la más utilizada es: tecnología de proxy dinámico
  • Generado por otros archivos, escenario típico: aplicación JSP
  • Extraiga archivos .class de bases de datos propietarias, relativamente raro
  • Obtenido de archivos cifrados, medidas típicas de protección contra la descompilación de archivos de Clase

3.2 Vinculación

3.2.1 Verificar

  • El propósito es garantizar que la información contenida en el flujo de bytes del archivo Class cumpla con los requisitos de la máquina virtual actual, para garantizar la exactitud de la clase cargada y no poner en peligro la seguridad de la máquina virtual en sí.
  • Incluye principalmente cuatro tipos de verificación: verificación de formato de archivo, verificación de metadatos, verificación de código de bytes y verificación de referencia de símbolo.
  • Verificación de formato: si comienza con magic oxCAFEBABE, si la versión principal y la versión secundaria están dentro del rango admitido por la máquina virtual Java actual, si cada elemento de los datos tiene la longitud correcta, etc.

inserte la descripción de la imagen aquí

3.2.2 Preparar

  • Asigne memoria para una variable de clase y establezca el valor inicial predeterminado de la variable, que es cero.
  • 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化.
    这里不会为实例变量分配初始化,Las variables de clase se asignan en el área de método, mientras que las variables de instancia se asignan en el montón de Java junto con el objeto.
  • Nota: Java no admite el tipo booleano. Para el tipo booleano, la implementación interna es int. Dado que el valor predeterminado de int es 0, el valor predeterminado de booleano es falso.

inserte la descripción de la imagen aquí

3.2.3 Resolver

  • El proceso de convertir referencias simbólicas en el conjunto de constantes en referencias directas.
  • De hecho, la operación de análisis suele ir acompañada de la ejecución de la JVM después de la inicialización.
  • Una referencia simbólica es un conjunto de símbolos que describen el objeto al que se hace referencia. La forma literal de la referencia del símbolo está claramente definida en el formato de archivo de clase de "Especificación de máquina virtual Java". Una referencia directa es un puntero directamente al objetivo, un desplazamiento relativo o un identificador que localiza indirectamente el objetivo.
  • La acción de análisis es principalmente para clases o interfaces, campos, métodos de clase, métodos de interfaz, tipos de métodos, etc. Correspondiente a CONSTANT_Class_info, CONSTANT_Fieldref_info, CONSTANT_Methodref_info, etc. en el grupo de constantes.
  • Las comillas simbólicas son: nombres completos de clases e interfaces, nombres y descriptores de campos, nombres y descriptores de métodos

Explique qué son las citas simbólicas y las citas directas.

  1. Hay un asiento vacío en el salón de clases, y el letrero en el asiento dice el asiento de Xiao Ming (referencia simbólica), y luego entra Xiao Ming, se sienta y deja caer el letrero (la referencia simbólica se reemplaza por una referencia directa)
  2. Cuando cocinamos, mira la receta, cuáles son los pasos (esta es la comilla simbólica), cuando realmente lo hacemos, se cita directamente el proceso
  3. Ejemplo: el código de bytes correspondiente a la operación de salida System.out.println():
    invoquevirtual #24 <java/io/PrintStream.println>

inserte la descripción de la imagen aquí

Tomando los métodos como ejemplo, la máquina virtual Java prepara una tabla de métodos para cada clase y enumera todos sus métodos en la tabla. Cuando necesita llamar a un método de una clase, solo necesita conocer la tabla de métodos. llamar a este método. A través de la operación de análisis, la referencia del símbolo se puede transformar en la posición del método de destino en la tabla de métodos de la clase, de modo que el método se llame con éxito.

3.3 Inicialización

  • Asigne a la variable de clase el valor de inicialización correcto.
  • La fase de inicialización es el proceso de ejecutar el constructor de clases clinit().
public class ClassInitTest {
private  static int num=1; //类变量的赋值动作

//静态代码快中的语句static{
num=2;
number=20;
System.out.println(num);//System.out.println(number); 报错:非法的前向引用

}

//Linking之prepare: number=0 -->initial:20-->10
private static int number=10;

public static void main(String[] args) {

System.out.println(ClassInitTest.num);
System.out.println(ClassInitTest.number);
}
}

inserte la descripción de la imagen aquí

  • No es necesario definir este método, es la combinación de las acciones de asignación de todas las variables de clase en la clase recopiladas automáticamente por el compilador javac y las declaraciones en el bloque de código estático.
  • Las instrucciones en un método constructor se ejecutan en el orden en que aparecen las declaraciones en el archivo fuente.
  • clinit() es diferente de un constructor de clases. (Asociación: el constructor es init() desde la perspectiva de la máquina virtual)
  • Si la clase tiene una clase principal, la JVM se asegurará de que se ejecute el clinit() de la clase principal antes de que se ejecute el clinit() de la subclase. clinit es diferente del constructor (init) de la clase (de padre a hijo, primero estático)
public class ClinitTest1 {

static class Father{
public static int A=1;
static{
A=2;
}
}

static class Son extends Father{
public static int B=A;

}

public static void main(String[] args) {
//这个输出2,则说明父类已经全部加载完毕
System.out.println(Son.B);
}

}
  • La máquina virtual debe asegurarse de que el método clinit() de una clase se bloquee sincrónicamente en subprocesos múltiples.
  • El compilador de Java no genera métodos de inicialización de clinit() para todas las clases. ¿Después de qué clases se compilan en bytecode, el archivo de bytecode no contendrá el método clinit()?
  1. Cuando una clase no declara ninguna variable de clase y no hay un bloque de código estático
  2. Cuando una variable de clase se declara en una clase, pero la declaración de inicialización de la variable de clase y el bloque de código estático no se usan explícitamente para realizar la operación de inicialización
  3. Una clase contiene campos de tipos de datos básicos modificados por static final, y estas declaraciones de inicialización de campo de clase usan expresiones constantes en tiempo de compilación (si static final no se pasa a través de un método o constructor, estará en la fase de vinculación)
/**
* @author TANGZHI* @create 2021-01-01 18:49
* 哪些场景下,java编译器就不会生成<clinit>()方法
*/

public class InitializationTest1 {
//场景1:对应非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
public int num = 1;

//场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法
public static int num1;

//场景3:比如对于声明为static final的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit()
//方法
public static final int num2 = 1;
}
  • El problema con la combinación de estático y final (uso estático + decoración final, y la asignación explícita del tipo de datos básico o tipo String que no implica una llamada de método o constructor en la asignación se lleva a cabo en el enlace de preparación de la etapa de enlace )

/*** @author TANGZHI* @create 2021-01-01 *
* 说明:使用static + final修饰的字段的显式赋值的操作,到底是在哪个阶段进行的赋值?
* 情况1:在链接阶段的准备环节赋值
* 情况2:在初始化阶段<clinit>()中赋值
* 结论:
* 在链接阶段的准备环节赋值的情况:
* 1. 对于基本数据类型的字段来说,如果使用static final修饰,则显式赋值(直接赋值常量,而非调用方法)通常是在链接阶段的准备环节进行
* 2. 对于String来说,如果使用字面量的方式赋值,使用static final修饰的话,则显式赋值通常是在链接阶
*段的准备环节进行
* 在初始化阶段<clinit>()中赋值的情况:
* 排除上述的在准备环节赋值的情况之外的情况。
* 最终结论:使用static + final修饰,且显示赋值中不涉及到方法或构造器调用的基本数据类型或String类型的显式赋值,是在链接阶段的准备环节进行。*/

public class InitializationTest2 {
public static int a = 1;

//在初始化阶段<clinit>()中赋值
public static final int INT_CONSTANT = 10;

//在链接阶段的准备环节赋值
public static final Integer INTEGER_CONSTANT1 = Integer.valueOf(100);

//在初始化阶段<clinit>()中赋值
public static Integer INTEGER_CONSTANT2 = Integer.valueOf(1000);

//在初始化阶段<clinit>()中赋值
public static final String s0 = "helloworld0";

//在链接阶段的准备环节赋值
public static final String s1 = new String("helloworld1");

//在初始化阶段<clinit>()中赋值
public static String s2 = "helloworld2";
public static final int NUM1 = new Random().nextInt(10);//在初始化阶段<clinit>()中赋值
}
  • ¿Se bloqueará la llamada a clinit()?
  1. La máquina virtual garantiza que el método () de una clase esté correctamente bloqueado y sincronizado en un entorno de subprocesos múltiples. Si varios subprocesos inicializan una clase al mismo tiempo, solo un subproceso ejecutará el método () de esta clase y otros subprocesos los subprocesos deberán bloquearse y esperar hasta que el subproceso activo ejecute el método ()
  2. Es precisamente porque la función () es segura para subprocesos con un bloqueo, por lo tanto, si hay una operación que consume mucho tiempo en el método () de una clase, puede causar que varios subprocesos se bloqueen y provoquen un interbloqueo. Y esos interbloqueos son difíciles de encontrar, porque parece que no tienen información de bloqueo disponible.

4. Clasificación del cargador de clase

  • La JVM admite dos tipos de cargadores de clases. Son Bootstrap ClassLoader y User-Defined ClassLoader respectivamente.
  • Conceptualmente hablando, un cargador de clases personalizado generalmente se refiere a un tipo de cargador de clases personalizado por el desarrollador en el programa, pero la especificación de máquina virtual de Java no lo define de esta manera, sino que carga todas las clases derivadas de la clase abstracta ClassLoader Classifiers se dividen en cargadores de clases personalizados.
  • No importa cómo se divida el tipo de cargador de clases, siempre hay solo tres de nuestros cargadores de clases más comunes en el programa, de la siguiente manera:

inserte la descripción de la imagen aquí
La relación entre los cuatro aquí es una relación de contención. No son las capas superior e inferior, ni tampoco la relación de herencia entre las clases padre e hijo.

4.1 El cargador que viene con la máquina virtual

Cargador de clases Bootstrap (Cargador de clases Bootstrap)

  • Esta carga de clase se implementa en lenguaje C/C++ y se anida dentro de la JVM.
  • Se utiliza para cargar la biblioteca central de Java (JAVA_HOME/jre/lib/rt.jar, resources.jar o contenido bajo la ruta sun.boot.class.path) para proporcionar las clases que necesita la propia JVM.
  • No hereda de ava.lang.ClassLoader, ningún cargador principal.
  • Cargue clases de extensión y cargadores de clases de aplicaciones, y especifíquelos como sus cargadores de clases principales.
  • Por razones de seguridad, el cargador de clases de inicio de Bootstrap solo carga clases cuyos nombres de paquetes comienzan con java, javax, sun, etc.

Extensión ClassLoader

  • Escrito en lenguaje Java, implementado por sun.misc.Launcher$ExtClassLoader.
  • Derivado de la clase ClassLoader
  • El cargador de clases principal es el cargador de clases de inicio
  • Cargue la biblioteca de clases desde el directorio especificado por la propiedad del sistema java.ext.dirs, o cargue la biblioteca de clases desde el subdirectorio jre/lib/ext (directorio de extensión) del directorio de instalación de JDK. Si los JAR creados por el usuario se colocan en este directorio, el cargador de clases de extensión también los cargará automáticamente.

4.2 Cargador de clases definido por el usuario

  • En el desarrollo diario de aplicaciones de Java, la carga de clases casi se realiza mediante la cooperación de los tres tipos de cargadores anteriores. Cuando sea necesario, también podemos personalizar el cargador de clases para personalizar la forma en que se carga la clase. ¿Por qué un cargador de clases personalizado?
  • Clases de carga de forma aislada
  • Modificar la forma en que se carga la clase.
  • Fuente de carga extendida
  • Evite la fuga de código fuente

Pasos de implementación del cargador de clases definido por el usuario:

  • Los desarrolladores pueden implementar sus propios cargadores de clases al heredar la clase abstracta ava.lang.ClassLoader para satisfacer algunas necesidades especiales.
  • Antes de JDK1.2, al personalizar el cargador de clases, herede siempre la clase ClassLoader y reescriba el método loadClass(), para realizar la clase de carga de clase personalizada, pero después de JDK1.2, ya no se recomienda que los usuarios anulen loadclass (), se recomienda escribir una lógica de carga de clase personalizada en el método findClass()
  • Al escribir un cargador de clases personalizado, si no hay requisitos demasiado complicados, puede heredar directamente la clase URLClassLoader, de modo que pueda evitar escribir el método findClass() y la forma de obtener el flujo de bytecode usted mismo, de modo que el cargador de clases personalizado Escribe de forma más concisa.
public class ClassLoaderDemo {

public static void main(String[] args) {

ClassLoader classloader1 = ClassLoader.getSystemClassLoader();
//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(classloader1);

//获取到扩展类加载器
//sun.misc.Launcher$ExtClassLoader@424c0bc4
System.out.println(classloader1.getParent());

//获取到引导类加载器 null
System.out.println(classloader1.getParent().getParent());

//获取系统的ClassLoader
ClassLoader classloader2 = Thread.currentThread().getContextClassLoader();

//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(classloader2);

String[]strArr=new String[10];
ClassLoader classLoader3 = strArr.getClass().getClassLoader();

//null,表示使用的是引导类加载器
System.out.println(classLoader3);

ClassLoaderDemo[]refArr=new ClassLoaderDemo[10];
//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(refArr.getClass().getClassLoader());

int[]intArr=new int[10];
//null,如果数组的元素类型是基本数据类型,数组类是没有类加载器的System.out.println(intArr.getClass().getClassLoader());
}
}

4.3 Instrucciones para usar ClassLoader

  • La clase ClassLoader es una clase abstracta, y todos los cargadores de clases subsiguientes heredan de ClassLoader (excluyendo el cargador de clases de inicio)

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
Formas de obtener ClassLoader

方式一:获取当前ClassLoader
clazz.getClassLoader()

方式二:获取当前线程上下文的ClassLoader 
Thread.currentThread().getContextClassLoader()

方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader()

方式四:获取调用者的ClassLoader 
DriverManager.getCallerClassLoader()

4.3 Mecanismo de delegación de los padres

La máquina virtual Java utiliza un método de carga bajo demanda para archivos de clase, es decir, cuando se necesita usar la clase, su archivo de clase se cargará en la memoria para generar un objeto de clase. Además, al cargar el archivo de clase de una determinada clase, la máquina virtual Java adopta el modo de delegación principal, es decir, la solicitud se entrega a la clase principal para su procesamiento, que es un modo de delegación de tareas.

principio de funcionamiento

  • Si un cargador de clases recibe una solicitud de carga de clases, no la carga primero, sino que delega la solicitud al cargador de clases principal para su ejecución;
  • Si el cargador de clases principal todavía tiene su cargador de clases principal, delegará más hacia arriba, se repetirá a su vez y la solicitud finalmente llegará al cargador de clases de inicio de nivel superior;
  • Si el cargador de clases principal puede completar la tarea de carga de clases, regresará con éxito. Si el cargador de clases principal no puede completar la tarea de carga, el cargador secundario intentará cargarlo por sí mismo. Este es el modo de delegación principal.

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

ejemplo

  • Cuando cargamos jdbc.jar para la conexión de la base de datos, lo primero que debemos saber es que jdbc.jar se implementa en función de la interfaz SPI, por lo que al cargar, se realizará la delegación principal y, finalmente, SPI se cargará desde el cargador raíz. La clase principal, y luego cargue la clase de interfaz SPI, y luego realice la delegación inversa, y cargue la clase de implementación jdbc.jar a través del cargador de clases de contexto de subprocesos.

inserte la descripción de la imagen aquí

Ventaja

  • Evite la carga duplicada de clases
  • Proteja la seguridad del programa y evite que la API central sea manipulada a voluntad
    • Clase personalizada: java.lang.String
    • Clase personalizada: java.lang.ShkStart (error: impidiendo la creación de clases que comienzan con java.lang)

Mecanismo de seguridad de la zona de pruebas

  • Personalice la clase String, pero al cargar la clase String personalizada, primero usará el cargador de clases de arranque para cargar, y el cargador de clases de arranque cargará primero los archivos que vienen con jdk durante el proceso de carga (java\lang en el archivo rt. jar paquete \String.class), el mensaje de error dice que no hay un método principal porque la clase de cadena en el paquete rt.jar está cargada. Esto puede garantizar la protección del código fuente principal de Java, que es el mecanismo de seguridad de la zona de pruebas.
  • Como se muestra en la figura, aunque hemos personalizado un String en java. Carga la clase cuyo nombre de paquete comienza con java, javax, sun, etc.), y el String en la biblioteca de clases principal no tiene un método principal

inserte la descripción de la imagen aquí

5. Otro

Cómo determinar si dos objetos de clase son iguales

En la JVM, existen dos condiciones necesarias para indicar si dos objetos de clase son de la misma clase:

  • El nombre de clase completo de la clase debe coincidir, incluido el nombre del paquete.
  • El ClassLoader (refiriéndose al objeto de instancia ClassLoader) que carga esta clase debe ser el mismo.

En otras palabras, en la JVM, incluso si estos dos objetos de clase (objetos de clase) se originan en el mismo archivo de clase y son cargados por la misma máquina virtual, siempre que los objetos de instancia de ClassLoader que los cargan sean diferentes, los dos objetos de clase también son diferentes.iguales.

una referencia al cargador de clases

La JVM debe saber si el cargador de arranque o el cargador de clases de usuario cargó un tipo. Si un cargador de clases de usuario carga un tipo, la JVM guardará una referencia al cargador de clases como parte de la información de tipo en el área de métodos. Al resolver una referencia de un tipo a otro, la JVM debe asegurarse de que los cargadores de clases para ambos tipos sean los mismos.

Uso activo y pasivo de las clases.

El uso de clases por parte de los programas Java se divide en: uso activo y uso pasivo.

El uso activo se puede dividir en siete situaciones:

  • Crear una instancia de la clase.
  • Acceda a una variable estática de una clase o interfaz, o asigne un valor a la variable estática
  • Llamar a un método estático de una clase
  • Reflexión (por ejemplo: Class.forName("com.atguigu.Test"))
  • Inicializar una subclase de una clase
  • La clase marcada como la clase de inicio cuando se inicia la máquina virtual Java
  • El soporte de lenguaje dinámico proporcionado por JDK 7:
    el resultado del análisis de la instancia java.lang.invoke.MethodHandle
    REF_getStatic, REF_putStatic, REF_invokeStatic manejar la clase correspondiente no se inicializa, luego se inicializa

Además de los siete casos anteriores, otras formas de usar las clases de Java se consideran como uso pasivo de la clase, lo que no conducirá a la inicialización de la clase.

Tres áreas de datos de tiempo de ejecución

1. Área de datos de tiempo de ejecución

1.1 Resumen

Esta sección habla principalmente sobre el área de datos de tiempo de ejecución, que es la parte de la figura a continuación, que es la etapa posterior a la finalización de la carga de la clase.
inserte la descripción de la imagen aquí

Cuando pasamos las etapas anteriores: carga de clase -> verificación -> preparación -> análisis -> inicialización, el motor de ejecución se usará para usar nuestra clase, y el motor de ejecución se usará hasta que ejecutemos el área de datos de tiempo
inserte la descripción de la imagen aquí

La memoria es un recurso muy importante del sistema. Es el almacén intermedio y el puente entre el disco duro y la CPU. Lleva la operación en tiempo real del sistema operativo y las aplicaciones. Operación eficiente y estable. Diferentes JVM tienen algunas diferencias en los métodos de división de memoria y mecanismos de gestión. Combinado con la especificación de máquina virtual JVM, analicemos el diseño de memoria JVM clásico.

inserte la descripción de la imagen aquí

Los datos que obtenemos a través del disco o de la red IO deben cargarse primero en la memoria, y luego la CPU obtiene los datos de la memoria para leerlos, es decir, la memoria actúa como un puente entre la CPU y el disco.

inserte la descripción de la imagen aquí

La máquina virtual Java define varios tipos de áreas de datos de tiempo de ejecución que se utilizarán durante la ejecución del programa, algunas de las cuales se crearán cuando se inicie la máquina virtual y se destruirán cuando se cierre la máquina virtual. Otras son correspondencia uno a uno con subprocesos, y estas áreas de datos correspondientes a subprocesos se crearán y destruirán a medida que los subprocesos comiencen y finalicen.

Los grises son privados para un solo hilo, los rojos son compartidos por múltiples hilos. Ahora mismo:

  • Cada subproceso: incluye de forma independiente el contador del programa, la pila y la pila local.
  • Compartir entre subprocesos: montón, memoria fuera del montón (generación permanente o metaespacio, caché de código)

inserte la descripción de la imagen aquí
Solo hay una instancia de tiempo de ejecución por JVM. Es el entorno de tiempo de ejecución, que es equivalente al marco en el medio de la estructura de la memoria: el entorno de tiempo de ejecución.
inserte la descripción de la imagen aquí

1.2 Hilos

  • Un hilo es una unidad de ejecución en un programa. JVM permite que una aplicación tenga múltiples hilos de ejecución en paralelo. En Hotspot JVM, cada subproceso se asigna directamente a un subproceso nativo del sistema operativo.
  • Cuando un subproceso de Java está listo para ejecutarse, también se crea al mismo tiempo un subproceso nativo del sistema operativo. Una vez que finaliza la ejecución del subproceso de Java, el subproceso nativo también se recicla.
  • El sistema operativo es responsable de programar todos los subprocesos para cualquier CPU disponible. Una vez que el subproceso nativo se inicializa con éxito, llama al método run() en el subproceso de Java.

1.3 Subproceso del sistema JVM

  • Si usa la consola o cualquier herramienta de depuración, puede ver que hay muchos subprocesos ejecutándose en segundo plano. Estos subprocesos de fondo no incluyen el subproceso principal que llama a public static void main(String[] args) y todos los subprocesos creados por el mismo subproceso principal.
  • Estos subprocesos principales del sistema en segundo plano son principalmente los siguientes en Hotspot JVM:
    • Hilo de máquina virtual: El funcionamiento de este hilo solo aparecerá cuando la JVM llegue a un punto seguro. La razón por la que estas operaciones tienen que ocurrir en diferentes subprocesos es que todos requieren que la JVM llegue a un punto seguro donde el montón no cambia. Este tipo de ejecución de subprocesos incluye la recolección de elementos no utilizados "detener el mundo", la recolección de pilas de subprocesos, la suspensión de subprocesos y la revocación de bloqueo parcial.
    • Subproceso de tarea periódica: este subproceso es la encarnación de eventos de período de tiempo (como interrupciones) y generalmente se utilizan para programar la ejecución de operaciones periódicas.
    • Subproceso GC: este subproceso proporciona soporte para diferentes tipos de comportamientos de recolección de elementos no utilizados en la JVM.
    • Subproceso del compilador: este subproceso compila bytecode en código nativo en tiempo de ejecución.
    • Subproceso de envío de señales: este subproceso recibe señales y las envía a la JVM, que las maneja internamente llamando a los métodos apropiados.

2. Contador de programa (registro de PC)

En el registro de contador de programa (registro de contador de programa) en la JVM, el nombre del registro se deriva del registro de la CPU, y el registro almacena la información de la escena relacionada con la instrucción. La CPU solo puede funcionar si tiene datos cargados en los registros. Aquí no se trata de un registro físico en un sentido amplio, puede ser más apropiado traducirlo a un contador de PC (o contador de instrucciones) (también llamado gancho de programa), y no es fácil causar algunos malentendidos innecesarios. El registro de PC en la JVM es una simulación abstracta del registro de PC físico.

inserte la descripción de la imagen aquí

efecto

  • El registro de PC se utiliza para almacenar la dirección que apunta a la siguiente instrucción, que es el código de instrucción que se ejecutará. La siguiente instrucción es leída por el motor de ejecución.
  • Características (subproceso privado, sin desbordamiento de memoria)
  • La implementación física del contador de programa se implementa en registros, la unidad de ejecución más rápida en toda la CPU.
  • es la única área que no tiene OOM en la especificación de la máquina virtual Java

inserte la descripción de la imagen aquí

  • Es un espacio de memoria tan pequeño que es casi insignificante. También es el área de almacenamiento más rápida.
  • En la especificación JVM, cada subproceso tiene su propio contador de programa, que es privado para el subproceso, y su ciclo de vida es consistente con el ciclo de vida del subproceso.
  • Solo hay un método ejecutándose en un hilo en cualquier momento, que es == el llamado método actual. == El contador del programa almacena la dirección de la instrucción JVM del método Java que está ejecutando el subproceso actual o, si se está ejecutando el método nativo, es un valor no especificado (indefinido).
  • Es un indicador del flujo de control del programa. Las funciones básicas como bifurcación, bucle, salto, manejo de excepciones y recuperación de subprocesos necesitan depender de este contador para completarse.
  • Cuando el intérprete de código de bytes funciona, selecciona la siguiente instrucción de código de bytes que se ejecutará cambiando el valor de este contador.
  • Es la única área en la especificación de máquina virtual de Java que no especifica ninguna condición de OutofMemoryError.

¿Cuál es el uso de usar el registro de PC para almacenar la dirección de instrucción de código de bytes? ¿Por qué usar el registro de PC para registrar la dirección de ejecución del hilo actual?

  • Debido a que la CPU necesita cambiar cada subproceso continuamente, después de volver a cambiar en este momento, debe saber dónde comenzar para continuar con la ejecución.
  • El intérprete de código de bytes de la JVM necesita cambiar el valor del registro de la PC para aclarar qué instrucción de código de bytes debe ejecutarse a continuación.

inserte la descripción de la imagen aquí

¿Por qué el registro de la PC está configurado como privado?

  • Todos sabemos que el llamado método de subprocesos múltiples solo ejecutará uno de los subprocesos en un período de tiempo específico.La CPU continuará cambiando de tareas, lo que inevitablemente conducirá a interrupciones o recuperaciones frecuentes. ¿ninguna diferencia? Para registrar con precisión la dirección de la instrucción de código de bytes actual que ejecuta cada subproceso, la mejor manera es, naturalmente, asignar un registro de PC para cada subproceso, de modo que se puedan realizar cálculos independientes entre cada subproceso, de modo que no haya ninguna situación. de interferencia mutua.
  • Debido a la limitación de los intervalos de tiempo de la CPU, durante la ejecución simultánea de muchos subprocesos, en un momento dado, un procesador o un núcleo en un procesador multinúcleo solo ejecutará una instrucción en un determinado subproceso.
  • Esto conducirá inevitablemente a frecuentes interrupciones o recuperaciones.¿Cómo asegurarse de que no haya diferencia? Después de crear cada subproceso, generará su propio contador de programa y marco de pila, y el contador de programa no se afecta entre sí entre los subprocesos.

segmento de tiempo de CPU

  • El intervalo de tiempo de la CPU es el tiempo asignado por la CPU a cada programa, y ​​a cada subproceso se le asigna un período de tiempo, denominado intervalo de tiempo.
  • A nivel macro: podemos abrir múltiples aplicaciones al mismo tiempo, y cada programa se ejecuta en paralelo y se ejecuta al mismo tiempo.
  • Pero a nivel micro: dado que solo hay una CPU, solo puede procesar una parte de los requisitos del programa a la vez.Cómo tratar con equidad, una forma es introducir intervalos de tiempo, y cada programa se ejecutará a su vez.

Cuatro pilas de máquinas virtuales

4.1 Descripción general de la pila de máquinas virtuales

4.1.1 Antecedentes

  • Debido al diseño multiplataforma, las instrucciones de Java se diseñan en función de la pila. La arquitectura de la CPU de las diferentes plataformas es diferente, por lo que no se puede diseñar como basada en registros.
  • La ventaja es que es multiplataforma, el conjunto de instrucciones es pequeño y el compilador es fácil de implementar, la desventaja es que el rendimiento disminuye y se necesitan más instrucciones para realizar la misma función.

4.1.2 Pila y montón en la memoria

  • La pila es la unidad de tiempo de ejecución, mientras que el montón es la unidad de almacenamiento.
  • La pila resuelve el problema de ejecución del programa, es decir, cómo se ejecuta el programa o cómo procesar los datos.
  • El montón resuelve el problema del almacenamiento de datos, es decir, cómo y dónde poner los datos.

4.1.3 Contenido básico de la pila de máquinas virtuales

¿Qué es la pila de máquinas virtuales de Java?

Pila de máquina virtual de Java (Java Virtual Machine Stack), también conocida como pila de Java en los primeros días. Cada subproceso creará una pila de máquina virtual cuando se cree, y cada marco de pila (Stack Frame) se almacena dentro, correspondiente a cada llamada de método Java, que es privado para el subproceso.

ciclo vital

El ciclo de vida es consistente con el hilo.

efecto

Encargado de la operación del programa Java, guarda las variables locales y resultados parciales del método, y participa en la llamada y devolución del método.

Las características de la pila.

  • La pila es una forma rápida y eficiente de asignar almacenamiento, y su velocidad de acceso solo es superada por el contador del programa.
  • Solo hay dos operaciones directas de la JVM en la pila de Java:
    • Cada método se ejecuta, acompañado de push (push, push)
    • Hacer estallar el trabajo después de la ejecución
  • No hay problema de recolección de basura para la pila ( la pila se desborda )

inserte la descripción de la imagen aquí
Excepciones que pueden ocurrir en la pila

  • La especificación de 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 Java de tamaño fijo, la capacidad de pila de la máquina virtual Java de cada subproceso se puede seleccionar de forma independiente cuando se crea el subproceso. Si el tamaño de pila solicitado por el subproceso supera la capacidad máxima permitida por la pila de la máquina virtual Java, la máquina virtual Java generará una excepción 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 si no hay suficiente memoria para crear la pila de la máquina virtual correspondiente al crear un nuevo subproceso, la máquina virtual Java generará un = =OutOfMemoryError == Excepción.
public static void main(String[] args) {

test();
}

public static void test() {
test();
}
//抛出异常:Exception in thread"main"java.lang.StackoverflowError
//程序不断的进行递归调用,而且没有退出条件,就会导致不断地进行压栈。

Establecer el tamaño de la memoria de pila

  • Podemos usar la opción del parámetro -Xss para establecer el espacio máximo de pila del subproceso, el tamaño de la pila determina directamente la profundidad máxima alcanzable de la llamada de función
  • ¿Cómo establecer el tamaño de la memoria de pila? -Tamaño Xss (es decir: -XX:ThreadStackSize)
    • Generalmente, el valor predeterminado es 512k-1024k, según el sistema operativo (antes de jdk5, el tamaño de pila predeterminado es 256k; después de jdk5, el tamaño de pila predeterminado es 1024k) El tamaño de la pila determina directamente la profundidad máxima alcanzable de la llamada de
      función
public class StackDeepTest{ 

private static int count=0; 
public static void recursion(){
count++; 
recursion(); 
}

public static void main(String args[]){
try{
recursion();
} catch (Throwable e){
System.out.println("deep of calling="+count); 
e.printstackTrace();}
}
}

4.2 La unidad de almacenamiento de la pila

4.2.1 ¿Qué se almacena en la pila?

  • Cada hilo tiene su propia pila, y los datos en la pila existen en el formato de un marco de pila (Stack Frame) .
  • Cada método que se ejecuta en este hilo corresponde a un marco de pila (Stack Frame).
  • Un marco de pila es un bloque de memoria y un conjunto de datos que mantiene diversa información de datos durante la ejecución del método.

4.2.2 Principio de funcionamiento de la pila

  • Solo hay dos operaciones directas de la JVM en la pila de Java, que es empujar y abrir el marco de la pila, siguiendo el principio de "primero en entrar, primero en salir"/"último en entrar, primero en salir".
  • En un subproceso activo, solo habrá un marco de pila activo en un momento dado. Es decir, sólo es válido el marco de la pila (marco de la pila superior) del método que se está ejecutando actualmente. Este marco de la pila se denomina marco de la pila actual (Marco actual), y el método correspondiente al marco de la pila actual es el método actual Método) La clase que define este método es la clase actual (Current Class).
  • Todas las instrucciones de código de bytes ejecutadas por el motor de ejecución solo operan en el marco de pila actual.
  • Si se llama a otros métodos en este método, se creará el nuevo marco de pila correspondiente y se colocará en la parte superior de la pila para convertirse en el nuevo marco actual.

inserte la descripción de la imagen aquí

  • No se permite que los marcos de pila contenidos en subprocesos diferentes tengan referencias mutuas, es decir, es imposible hacer referencia al 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, haciendo que el marco de pila anterior se convierta en el marco de pila anterior. marco de pila actual de nuevo.
  • Hay dos formas de devolver una función en un método Java, una es una devolución de función normal, usando la instrucción de devolución; la otra es lanzar una excepción. De cualquier manera, el marco de la pila aparecerá.

4.2.3 La estructura interna del marco de la pila

Cada marco de pila almacena:

  • Tabla de variables locales (Variables locales)
  • Vinculación dinámica (o una referencia de método al conjunto de constantes de tiempo de ejecución)
  • Vinculación dinámica (o una referencia de método al conjunto de constantes de tiempo de ejecución)
  • Dirección de retorno del método (Dirección de retorno) (o la definición de salida del método normal o anormal)
  • alguna información adicional

inserte la descripción de la imagen aquí
La pila debajo de cada subproceso paralelo es privada, por lo que cada subproceso tiene su propia pila y hay muchos marcos de pila en cada pila. El tamaño del marco de pila está determinado principalmente por la tabla de variables locales y la pila de operandos.
inserte la descripción de la imagen aquí

4.3 Tabla de Variables Locales (Variables Locales)

  • Las tablas de variables locales también se denominan matrices de variables locales o tablas de variables locales.
  • Definido como una matriz numérica, se utiliza principalmente para almacenar parámetros de métodos y variables locales definidas en el cuerpo del método.Estos tipos de datos incluyen varios tipos de datos básicos, referencias a objetos (referencia) y tipos returnAddress.
  • Dado que la tabla de variables locales se basa en la pila del subproceso, son los datos privados del subproceso, == por lo que no hay ningún problema de seguridad de datos ==
  • == La capacidad requerida de la tabla de variables locales se determina en tiempo de 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.
  • El número de llamadas anidadas a métodos está determinado por el tamaño de la pila. En términos generales, cuanto más grande es la pila ==, más veces se anida y se llama al método. == Para una función, cuantos más parámetros y variables locales tenga, más grande será la tabla de variables locales y más grande será su marco de pila, para satisfacer la creciente demanda de información pasada por la llamada al método. A su vez, las llamadas a funciones ocuparán más espacio en la pila, lo que resultará en una reducción del número de llamadas anidadas.
  • == 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.
//使用javap -v 类.class 或者使用jclasslib
public class LocalVariableTest {

public static void main(String[] args) {
LocalVariableTest test=new LocalVariableTest();
int num=10;
test.test1();
}

public static void test1(){
Date date=new Date();
String name="xiaozhi";
}
}

La captura de pantalla de jclasslib es la siguiente:
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.3.1 Entendimiento sobre Slot

  • Tabla de variables locales, la unidad de almacenamiento más básica es Slot (ranura variable)
  • El almacenamiento del valor del parámetro siempre comienza en el índice 0 de la matriz de variables locales y finaliza en el índice de la longitud de la matriz -1.
  • Varios tipos de datos básicos (8 tipos), tipo de referencia (referencia) y variables de tipo returnAddress conocidas en el momento de la compilación se almacenan en la tabla de variables locales.
  • 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) ocupan dos ranuras.
  • byte, short y char se convierten a int antes del almacenamiento, boolean también se convierte a int, 0 significa falso, distinto de cero significa verdadero.
  • La JVM asignará un índice de acceso a cada Ranura en la tabla de variables locales, a través del cual se puede acceder con éxito al valor de la variable local especificado en la tabla de variables locales.
  • Cuando se llama a un método de instancia, sus parámetros de método y las variables locales definidas dentro del cuerpo del método se copiarán en cada ranura de la tabla de variables locales en orden.
  • Si necesita acceder a un valor de variable local de 64 bits en la tabla de variables locales, solo necesita usar el índice anterior. (Por ejemplo: acceso a variables de tipo largo o doble)
  • Si el marco actual se crea mediante un método de construcción o un método de instancia, entonces la referencia del objeto se almacenará 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.

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

4.3.2 Reutilización de ranuras

Las ranuras en la tabla de variables locales en el marco de la pila se pueden reutilizar.Si una variable local ha superado su alcance, es probable que la nueva variable local declarada después de su alcance reutilice la ranura de la variable local caducada.bit, para lograr el propósito de ahorrar recursos.

public class SlotTest {

public void localVarl() {

int a = 0;

System.out.println(a);

int b = 0;

}

public void localVar2() {

int a = 0;
System.out.println(a);

}//此时的就会复用a的槽位int b = 0;

}
}

4.3.3 Variables estáticas vs variables locales

  • Una vez asignada la lista de parámetros, se asigna de acuerdo con el orden y el alcance de las variables definidas en el cuerpo del método.
  • Sabemos que la tabla de variables de clase tiene dos oportunidades para inicializar, la primera vez está en la == "fase de preparación", se realiza la inicialización del sistema y se establece un valor cero para la variable de clase, y la otra está en la " inicialización" == fase, que se le da al programador Valor inicial definido en el código.
  • A diferencia de la inicialización de variables de clase, la tabla de variables locales no tiene un proceso de inicialización del sistema, lo que significa que una vez que se define una variable local, debe inicializarse manualmente, de lo contrario no se puede utilizar.
//这样的代码是错误的,没有赋值不能够使用。
public void test(){int i;System. out. println(i);
}

Nota complementaria

  • En el marco de la pila, la parte más estrechamente relacionada con el ajuste del rendimiento es la tabla de variables locales mencionada anteriormente. 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 de la tabla de variables locales también son importantes nodos raíz de recolección de elementos no utilizados, siempre que los objetos a los que se hace referencia directa o indirectamente en la tabla de variables locales no se reciclen.

4.4 Pila de operandos

  • Además de la tabla de variables locales, cada marco de pila independiente también contiene una pila de operandos de tipo último en entrar, primero en salir (Last-In-First-Out), que también se puede denominar pila de expresiones (Pila de expresiones).
  • Pila de operandos, en el proceso de ejecución del método, de acuerdo con la instrucción del código de bytes, escriba datos en la pila o extraiga datos, es decir, empuje (push) y pop (pop)
    • Algunas instrucciones de bytecode insertan valores en la pila de operandos, otras extraen operandos de la pila. Úsalos y empuja el resultado a la pila
    • Por ejemplo: realizar operaciones como copiar, intercambiar y sumar

inserte la descripción de la imagen aquí

public void testAddOperation(){byte i = 15; int j = 8; int k = i + j;
}
public void testAddOperation();
 Code:
0: bipush 15
2: istore_1 
3: bipush 8
5: istore_2 
6:iload_1 
7:iload_2 
8:iadd
9:istore_3 
10:return
  • 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 de JVM. Cuando un método comienza a ejecutarse, se creará un nuevo marco de pila en consecuencia. La pila de operandos de este método está vacía.
  • Cada pila de operandos tendrá una profundidad de pila clara para almacenar valores. La profundidad máxima requerida se define en el momento de la compilación y se almacena en el atributo Code del método, que es el valor de max_stack.
  • Cualquier elemento en la pila puede ser cualquier tipo de datos Java
    • El tipo de 32 bits ocupa una profundidad de unidad de pila
    • El tipo de 64 bits ocupa dos profundidades de unidad de pila
  • La pila de operandos no accede a los datos accediendo al índice, pero solo puede completar un acceso a los datos a través de operaciones push y pop estándar.
  • 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 siguiente instrucción de código de bytes que se ejecutará en el registro de PC.
  • El tipo de datos de los elementos en la pila de operandos debe coincidir estrictamente con la secuencia de instrucciones de código de bytes, que el compilador verifica durante el compilador y verifica nuevamente durante la etapa de análisis de flujo de datos de la etapa de verificación de clase en el proceso de carga de clase.
  • Además, decimos que el motor de interpretación de la máquina virtual Java es un motor de ejecución basado en pila, donde la pila se refiere a la pila de operandos.

4.5 Seguimiento de código

public void testAddOperation() {byte i = 15;int j = 8;int k = i + j;
}

Use el comando javap para descompilar el archivo de clase: javap -v class name.class

public void testAddoperation(); 
Code:
0: bipush 15 
2: istore_1 
3: bipush 8
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: return

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í
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.6 Tecnología de cobro Top Of Stack

  • Como se mencionó anteriormente, las instrucciones de dirección cero utilizadas por la máquina virtual basada en la arquitectura de pila son más compactas, pero es necesario usar más instrucciones push y pop para completar una operación, lo que también significa que se requerirán más instrucciones. número de envío de instrucciones y tiempos de lectura/escritura de memoria.
  • Dado que los operandos se almacenan en la memoria, la ejecución frecuente de operaciones de lectura/escritura en la memoria afectará inevitablemente la velocidad de ejecución. Para resolver este problema, los diseñadores de HotSpot JVM propusieron la tecnología de caché de la parte superior de la pila (Tos, Top-of-Stack Cashing), que almacena en caché todos los elementos superiores de la pila en los registros de la CPU física, por lo que reduciendo los tiempos de lectura/escritura de la memoria para mejorar la eficiencia de ejecución del motor de ejecución.

4.7 Enlace dinámico

  • Enlace dinámico, dirección de retorno del método, información adicional: algunos lugares se denominan área de datos del marco
  • Cada marco de pila contiene internamente una referencia al método al que pertenece el marco de pila en el conjunto . El propósito de incluir esta referencia es apoyar el código del método actual para lograr la vinculación dinámica (Dynamic Linking). 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 la función del enlace dinámico es convertir estas referencias simbólicas en referencias directas al método que llama.

inserte la descripción de la imagen aquí

¿Por qué necesitamos un grupo de constantes de tiempo de ejecución?
La función del conjunto de constantes es proporcionar algunos símbolos y constantes para facilitar la identificación de las instrucciones.
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

4.8 Llamada al método

En la JVM, la conversión de referencias simbólicas a métodos de llamada de referencias directas está relacionada con el mecanismo de vinculación del 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, se llama al proceso de convertir la referencia simbólica del método de llamada en una referencia directa para vinculación estática

Enlace dinámico:

Si el método llamado no se puede determinar durante la compilación, el símbolo del método llamado solo se puede convertir en una referencia directa durante el tiempo de ejecución del programa. Dado que este proceso de conversión de referencia es dinámico, también se denomina vinculación dinámica.

La vinculación estática y la vinculación dinámica no son sustantivos, sino verbos, que es la clave para la comprensión.

El mecanismo de enlace del método correspondiente es: enlace temprano (Early Binding) y enlace tardío (Late Binding). La vinculación es un proceso en el que una referencia simbólica a un campo, método o clase se reemplaza por una referencia directa, lo que ocurre solo una vez.

Unión anticipada:

El enlace anticipado significa que si el método de destino llamado se conoce en el momento de la compilación y permanece sin cambios en el tiempo de ejecución, el método se puede vincular al tipo al que pertenece. Cuál es, por lo que puede usar el enlace estático para convertir las referencias simbólicas en referencias directas. referencias

encuadernación tardía

Si el método llamado no se puede determinar en tiempo de compilación, el método relacionado solo se puede vincular de acuerdo con el tipo real en tiempo de ejecución del programa.Este método de vinculación también se denomina vinculación tardía.

Con la aparición de los lenguajes de alto nivel, existen cada vez más lenguajes de programación orientados a objetos similares a Java. Aunque estos lenguajes de programación tienen ciertas diferencias en el estilo gramatical, siempre mantienen una similitud entre sí, es decir. , todos admiten funciones orientadas a objetos, como encapsulación, herencia y polimorfismo. Dado que este tipo de lenguaje de programación tiene funciones polimórficas, naturalmente tiene dos métodos de enlace: enlace temprano y enlace tardío.

Cualquier método ordinario en Java en realidad tiene las características de las funciones virtuales, que son equivalentes a las funciones virtuales en lenguaje C (en C, debe usar la palabra clave virtual para definir explícitamente). Si no desea que un método tenga las características de una función virtual en un programa Java, puede utilizar la palabra clave final para marcar este método.

4.8.1 Métodos virtuales y no virtuales

Método no virtual:

  • Si el método determina la versión de llamada específica en tiempo de compilación, esta versión es inmutable en tiempo de ejecución. Estos métodos se denominan métodos no virtuales.
  • Los métodos estáticos, métodos privados, métodos finales, constructores de instancias y métodos de superclase son todos métodos no virtuales. Otros métodos se denominan métodos virtuales.

Se puede analizar en la fase de análisis de la carga de clases, el siguiente es un ejemplo de un método no virtual

class Father{
public static void print(String str){
System. out. println("father "+str); 
}

private void show(String str){

System. out. println("father"+str);

}
}

class Son extends Father{

public class VirtualMethodTest{

public static void main(String[] args){
Son.print("coder");
//Father fa=new Father();
//fa.show("atguigu.com");}
}

Las siguientes instrucciones de llamada de método se proporcionan en la máquina virtual:

Instrucciones de llamada ordinarias:

  • invocar estático: invoca un método estático y la fase de análisis determina la única versión del método
  • 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
  • invocarvirtual: llamar a todos los métodos virtuales
  • invocar interfaz: método de interfaz de llamada

Instrucción de llamada dinámica:

  • invoquedynamic: analice dinámicamente el método que debe llamarse y luego ejecute

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, mientras que la instrucción invocación dinámica ayuda al usuario a determinar la versión del método. Entre ellos, los métodos invocados por la instrucción invocar estática y la instrucción invocar especial se denominan métodos no virtuales, y el resto (excepto los modificados por fina1) se denominan métodos virtuales.

/*** 解析调用中非虚方法、虚方法的测试*/
class Father {

public Father(){
System.out.println("Father默认构造器");
}

public static void showStatic(String s){
System.out.println("Father show static"+s);
}

public final void showFinal(){
System.out.println("Father show final");
}

public void showCommon(){
System.out.println("Father show common");
}
}

public class Son extends Father{

public Son(){super();}

public Son(int age){
this();
}

public static void main(String[] args) {
Son son = new Son();
son.show();
}

//不是重写的父类方法,因为静态方法不能被重写
public static void showStatic(String s){
System.out.println("Son show static"+s);
}

private void showPrivate(String s){
System.out.println("Son show private"+s);
}

public void show(){
//invokestaticshowStatic(" 大头儿子");
//invokestaticsuper.showStatic(" 大头儿子");
//invokespecialshowPrivate(" hello!");
//invokespecialsuper.showCommon();
//invokevirtual 因为此方法声明有final 不能被子类重写,所以也认为该方法是非虚方法showFinal();
//虚方法如下
//invokevirtualshowCommon();/
/没有显式加super,被认为是虚方法,因为子类可能重写
showCommoninfo();
MethodInterface in = null;
//invokeinterface  不确定接口实现类是哪一个 需要重写
in.methodA();
}

public void info(){}}

interface MethodInterface {void methodA();
}

Acerca del comando invocado dinámico

  • El conjunto de instrucciones de código de bytes de JVM siempre ha sido relativamente estable. No fue hasta Java 7 que se agregó una instrucción de invocación dinámica. Esta es una mejora realizada por Java para admitir el "lenguaje de tipo dinámico".
  • Sin embargo, Java7 no proporciona un método para generar directamente instrucciones de invocación dinámica y es necesario utilizar ASM, una herramienta de código de bytes de bajo nivel, para generar instrucciones de invocación dinámica. Hasta la aparición de la expresión Lambda de Java8 y la generación de instrucciones de invocación dinámica, no había una forma directa de generarlas en Java.
  • La esencia del soporte de tipo de lenguaje dinámico agregado en Java 7 es la modificación de la especificación de la máquina virtual Java, no la modificación de las reglas del lenguaje Java. Esta parte es relativamente complicada y se agrega la llamada al método en la máquina virtual. La forma más directa beneficiario Es un compilador de lenguaje dinámico que se ejecuta en la plataforma Java.

Lenguaje tecleado dinámicamente y lenguaje tecleado estáticamente

  • La diferencia entre un lenguaje de tipado dinámico y uno de tipado estático radica en si el tipo se verifica en tiempo de compilación o en tiempo de ejecución.Si se cumple el primero, es un lenguaje de tipado estático, y viceversa, es un lenguaje de tipado dinámico.
  • Para decirlo más claramente, un lenguaje de tipo estático es para juzgar la información de tipo de la variable misma; un lenguaje de tipo dinámico es para juzgar la información de tipo del valor de la variable, la variable no tiene información de tipo y el valor de la variable tiene información de tipo , que es una característica importante del lenguaje dinámico .
  • Java es un lenguaje de tipo estático (aunque las expresiones lambda le agregan características dinámicas), js, python son lenguajes de tipo dinámico
	Java:String info = "小智";

//静态语言JS:
var name = "小智“;
var name = 10;

//动态语言Pythom: 
info = 130;//更加彻底的动态语言

La esencia de la reescritura de métodos en el lenguaje Java:

  • El registro de PC se cambiará cada vez que se ejecute una instrucción, y la dirección de retorno siempre será la dirección después de la llamada anterior antes de llamar a la llamada, y no cambiará.
  • Encuentre el tipo real del objeto ejecutado por el primer elemento en la parte superior de la pila de operandos, denotado C.
  • Si un método que coincide con la descripción en la constante y el nombre simple se encuentra en el tipo C, se realiza la verificación de la autoridad de acceso y, si pasa, se devuelve la referencia directa de este método y finaliza el proceso de búsqueda; si no , devuelve la excepción java.lang.IlegalAccessError.
  • De lo contrario, según la relación de herencia de abajo hacia arriba, realice el proceso de búsqueda y verificación en el segundo paso para cada clase padre de C.
  • Si no se encuentra un método adecuado, se lanza una excepción java.1ang.AbstractMethodserror.

Introducción a IllegalAccessError

  • El programa intentó acceder o modificar una propiedad o llamar a un método para el que no tiene permiso de acceso. Normalmente, esto provocará una excepción del compilador. Este error, si ocurre en tiempo de ejecución, indica que se ha producido un cambio incompatible en una clase.

4.8.2 Llamada de método: tabla de método virtual

  • En la programación orientada a objetos, el envío dinámico se usa con frecuencia. Si tiene que buscar un destino adecuado en los metadatos del método de la clase durante cada envío dinámico, puede afectar la eficiencia de la ejecución. Por lo tanto, para mejorar el rendimiento, la JVM lo implementa creando una tabla de métodos virtuales (virtual method table) en el área de métodos de la clase (los métodos no virtuales no aparecerán en la tabla). 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.
  • ¿Cuándo se creó la tabla de métodos virtuales?
    • La tabla de métodos virtuales se creará e inicializará durante la fase de vinculación de la 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.

inserte la descripción de la imagen aquí

interface Friendly{void sayHello();void sayGoodbye(); 
}
class Dog{public void sayHello(){}public String tostring(){return "Dog";}
}
class Cat implements Friendly {public void eat() {}public void sayHello() { } public void sayGoodbye() {}protected void finalize() {}
}
class CockerSpaniel extends Dog implements Friendly{public void sayHello() { super.sayHello();}public void sayGoodbye() {}
}

inserte la descripción de la imagen aquí

4.9 Dirección de retorno del método (return address)

  • Almacene el valor del registro de pc llamando a este método. Hay dos formas de finalizar un método:
    • ejecución normal completada
    • Ocurrió una excepción no controlada, salida anormal
  • No importa qué método se use para salir, después de que el método sale, regresa al lugar donde se llamó al método. Cuando el método sale normalmente, el valor del contador de PC de la persona que llama se utiliza como dirección de retorno, es decir, la dirección de la siguiente instrucción después de la instrucción que llama al método. Para aquellos que salen por excepción, la dirección de retorno debe determinarse a través de la tabla de excepciones, y esta parte de la información generalmente no se guarda en el marco de la pila.
  • En esencia, la salida del método es el proceso de abrir el marco de la pila actual. En este momento, es necesario restaurar la tabla de variables locales del método superior, la pila de operandos, empujar el valor devuelto a la pila de operandos del el marco de pila de la persona que llama, establezca el valor de registro de la PC, etc., para que el método de llamada continúe ejecutándose.
  • La diferencia entre la salida de finalización normal y la salida de finalización anormal es que la salida a través de la salida de finalización de excepción no producirá ningún valor de retorno a su llamador superior.

Después de que un método comienza a ejecutarse, solo hay dos formas de salir del método:

  • Cuando el motor de ejecución encuentra una instrucción de código de bytes (retorno) devuelta por cualquier método, el valor de retorno se pasará al llamador del método de capa superior, denominado salida de finalización normal;
    • Después de llamar normalmente a un método, qué instrucción de retorno debe usarse depende del tipo de datos real del valor de retorno del método.
    • En las instrucciones de bytecode, las instrucciones de retorno incluyen ireturn (usado cuando el valor de retorno es booleano, byte, char, short e int), lreturn (tipo largo), freturn (tipo flotante), dreturn (tipo doble) y areturn. Además, hay un método declarado como nulo por la instrucción de retorno, que es utilizado por los métodos de inicialización de instancias, métodos de inicialización de clase e interfaz.
  • Se encuentra una excepción (Exception) durante la ejecución del método, y la excepción no se maneja dentro del método, es decir, siempre que no se encuentre un controlador de excepciones coincidente en la tabla de excepciones del método , el método saldrá, referido como salida de finalización anormal.

inserte la descripción de la imagen aquí

Durante la ejecución del método, el manejo de excepciones cuando se lanza una excepción se almacena en una tabla de manejo de excepciones, por lo que es conveniente encontrar el código de manejo de excepciones cuando ocurre una excepción.
inserte la descripción de la imagen aquí

En esencia, la salida del método es el proceso de abrir el marco de pila actual. En este momento, es necesario restaurar la tabla de variables locales del método superior, la pila de operandos, la pila de operandos que empuja el valor devuelto al marco de la pila de la persona que llama, establecer el valor de registro de la PC, etc., para que el método de la persona que llama puede seguir ejecutándose.

La diferencia entre la salida de finalización normal y la salida de finalización anormal es que la salida a través de la salida de finalización anormal no producirá ningún valor de retorno a su llamador superior.

4.10 Alguna información adicional

También se permite llevar en el marco de la pila cierta información adicional relacionada con la implementación de la máquina virtual Java. Por ejemplo: información que brinda soporte para la depuración de programas.

inserte la descripción de la imagen aquí
Pila de preguntas de entrevista relacionadas

  • ¿Qué pasa con el desbordamiento de pila?Desbordamiento de pila: StackOverflowError
    • No hay GC en la pila, hay OOM y StackOverflowError
    • Para dar un ejemplo simple: llamar al método principal en el método principal continuará empujando la pila hasta que se desborde;
  • El tamaño de la pila se puede fijar o cambiar dinámicamente (expansión dinámica)
    • Si se corrige, se lanzará un StackOverflowError
    • Si se extiende dinámicamente, se lanzará una excepción OOM (java.lang.OutOfMemoryError)
  • ¿Ejemplo de desbordamiento de pila? (Error de desbordamiento de pila)
    • Establecer el tamaño de la pila por -Xss
  • ¿Puede el ajuste del tamaño de la pila asegurar que no habrá desbordamiento?
    • no puedo. Debido a que ajustar el tamaño de la pila solo reducirá la posibilidad de desbordamiento, el tamaño de la pila no se puede expandir infinitamente, por lo que no hay garantía de que no se produzca un desbordamiento.
  • ¿Cuanto mayor sea la memoria de pila asignada, mejor?
    • No, la probabilidad de OOM se reduce durante un cierto período de tiempo, pero ocupará otros espacios de subprocesos porque todo el espacio es limitado.
  • ¿La recolección de elementos no utilizados involucra la pila de la máquina virtual?
    • No, la recolección de elementos no utilizados solo involucrará el área de método y el montón, y el área de método y el montón también pueden desbordarse.
    • Contador de programa, solo registra la dirección de ejecución de la siguiente línea, no hay desbordamiento ni recolección de basura
    • La pila de la máquina virtual y la pila del método local solo implican empujar y sacar la pila, puede haber un desbordamiento de la pila y no hay recolección de basura
  • ¿Las variables locales definidas en los métodos son seguras para subprocesos?
    • Analizar temas específicos. Si el objeto se genera internamente y muere internamente sin ser devuelto al exterior, entonces es seguro para subprocesos; de lo contrario, no es seguro para subprocesos.
/**方法中定义的局部变量是否线程安全?   具体问题具体分析
* @author shkstart* @create 15:53
*/
public class LocalVariableThreadSafe {

//s1的声明方式是线程安全的,因为线程私有,在线程内创建的s1 ,不会被其它线程调用
public static void method1() {
//StringBuilder:线程不安全
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
//...}

//stringBuilder的操作过程:是线程不安全的,
// 因为stringBuilder是外面传进来的,有可能被多个线程调用
public static void method2(StringBuilder stringBuilder) {
stringBuilder.append("a");
stringBuilder.append("b");
//...}

//stringBuilder的操作:是线程不安全的;因为返回了一个stringBuilder,
// stringBuilder有可能被其他线程共享
public static StringBuilder method3() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("a");
stringBuilder.append("b");
return stringBuilder;
}

//stringBuilder的操作:是线程安全的;因为返回了一个stringBuilder.toString()相当于new了一个String,
// 所以stringBuilder没有被其他线程共享的可能
public static String method4() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("a");
stringBuilder.append("b");
return stringBuilder.toString();
/*** 结论:如果局部变量在内部产生并在内部消亡的,那就是线程安全的*/
}
}
área de datos de tiempo de ejecución ¿Hay un error? ¿Hay un GC?
contador de programa No No
pila de máquinas virtuales Sí (SOE) No
pila de métodos nativos No
área de método Sí (OOM)
montón

Cinco interfaces de métodos nativos y pilas de métodos nativos

5.1 Interfaz de método nativo

inserte la descripción de la imagen aquí
¿Qué son los métodos nativos?

En pocas palabras, un método nativo es una interfaz para que Java llame a código que no es de Java. Un método nativo es un método Java cuya implementación se implementa en un lenguaje que no es Java, como C. Esta característica no es exclusiva de Java. Muchos otros lenguajes de programación tienen este mecanismo. Por ejemplo, en C, puede usar extern "c" para decirle al compilador c que llame a una función c.

Al definir un método nativo, no se proporciona el cuerpo de implementación (algo así como definir una interfaz Java), porque el cuerpo de implementación se implementa mediante un lenguaje externo que no es Java.

El rol de la interfaz local es integrar diferentes lenguajes de programación para Java, y su intención original es integrar programas C/C++.

//标识符native可以与其它java标识符连用,但是abstract除外
public class IHaveNatives{

public native void methodNative1(int x);

public native static long methodNative2();

private native synchronized float methodNative3(Object o);

native void methodNative4(int[] ary) throws Exception;
}

¿Por qué usar el método nativo?

Java es muy cómodo de usar, pero no es fácil implementar ciertos niveles de tareas en Java, o cuando nos preocupamos por la eficiencia del programa, surgen problemas.

Interacción con el entorno Java

A veces, las aplicaciones de Java necesitan interactuar con el entorno fuera de Java, que es la razón principal de la existencia de métodos nativos. Puede pensar en la situación en la que Java necesita intercambiar información con algún sistema subyacente, como un sistema operativo o algún hardware. El método nativo es solo un mecanismo de comunicación de este tipo: nos proporciona una interfaz muy concisa y no necesitamos entender los tediosos detalles fuera de la aplicación Java.

Interacción con el sistema operativo

La JVM soporta el propio lenguaje Java y la biblioteca de tiempo de ejecución, es la plataforma sobre la que viven los programas Java, consta de un intérprete (interpretando bytecode) y unas bibliotecas conectadas a código nativo. Sin embargo, después de todo, no es un sistema completo y, a menudo, depende del soporte de un sistema subyacente. Estos sistemas subyacentes suelen ser sistemas operativos potentes. Al usar métodos nativos, podemos usar Java para realizar la interacción entre jre y el sistema subyacente, e incluso algunas partes de JVM están escritas en c. Además, si queremos usar algunas características del sistema operativo encapsulado que el propio lenguaje Java no proporciona, también necesitamos usar métodos nativos.

Java del sol

El intérprete de Sun está implementado en C, lo que le permite interactuar con el mundo exterior como un C ordinario. La mayor parte del jre está implementado en Java y también interactúa con el mundo exterior a través de algunos métodos nativos. Por ejemplo: el método setPriority() de la clase java.lang.Thread está implementado en Java, pero llama al método local setPriority() en la clase. Este método nativo se implementa en C y se implanta dentro de la JVM En la plataforma Windows 95, este método nativo eventualmente llamará a la API Win32 setPriority(). Esta es una implementación específica de un método local proporcionado directamente por la JVM. Más a menudo, el método local lo proporciona una biblioteca de vínculos dinámicos externa (biblioteca de vínculos dinámicos externa) y luego es llamado por JVw.

status quo

En la actualidad, este método se usa cada vez menos, excepto para aplicaciones relacionadas con el hardware, como controlar impresoras a través de programas Java o administrar equipos de producción a través de sistemas Java, que son relativamente raros en las aplicaciones de nivel empresarial. Debido a que la comunicación entre campos heterogéneos está muy desarrollada ahora, por ejemplo, se puede usar la comunicación de socket, y también se puede usar el servicio web, etc., por lo que no introduciré mucho.

5.2 Pila de métodos nativos

  • La pila de la máquina virtual de Java se usa para administrar las llamadas de los métodos de Java, y la pila de métodos nativos se usa para administrar las llamadas de los métodos nativos.
  • La pila de métodos nativos también es privada para subprocesos.
  • Permite implementarse como tamaño de memoria fijo o expandible dinámicamente. (lo mismo en términos de falta de memoria)
    • Si el tamaño de pila solicitado por el subproceso supera el tamaño máximo permitido por la pila de métodos nativos, la máquina virtual Java generará una excepción StackOverflowError.
    • Si la pila de métodos nativos se puede expandir dinámicamente y no puede solicitar suficiente memoria al intentar expandirse, o si no hay suficiente memoria para crear la pila de métodos nativos correspondiente al crear un nuevo subproceso, la máquina virtual de Java generará un OutOfMemoryError. anormal.
  • Los métodos nativos se implementan utilizando el lenguaje C.
  • Su método específico es registrar el método nativo en la pila de métodos nativos y cargar la biblioteca de métodos nativos cuando se ejecuta el motor de ejecución.

inserte la descripción de la imagen aquí

  • Cuando un subproceso llama a un método nativo, ingresa a un mundo completamente nuevo que ya no está limitado por la máquina virtual. Tiene los mismos permisos que la máquina virtual.
    • Los métodos nativos pueden acceder al área de datos de tiempo de ejecución dentro de la máquina virtual a través de la interfaz del método nativo.
    • Incluso puede usar registros directamente en el procesador local
    • Asigne cualquier cantidad de memoria directamente desde el montón en la memoria nativa.
  • No todas las JVM admiten métodos nativos. Porque la especificación de la máquina virtual de Java no requiere claramente el lenguaje utilizado, los métodos de implementación específicos, las estructuras de datos, etc. de la pila de métodos locales. Si el producto JVM no tiene previsto admitir métodos nativos, no es necesario implementar la pila de métodos nativos.
  • En Hotspot JVM, la pila de métodos locales y la pila de máquinas virtuales se combinan directamente en una sola.

Seis Montón

6.1 Descripción general del núcleo de Heap

El almacenamiento dinámico es único para un proceso de JVM, es decir, un proceso tiene solo una JVM, pero el proceso contiene varios subprocesos y comparten el mismo espacio de almacenamiento dinámico.

inserte la descripción de la imagen aquí

  • Solo hay una memoria de almacenamiento dinámico en una instancia de JVM, y el almacenamiento dinámico también es el área central de la gestión de memoria de Java.
  • El área de almacenamiento dinámico de Java se crea cuando se inicia la JVM y se determina el tamaño de su espacio. Es el espacio de memoria más grande administrado por la JVM.
    • El tamaño de la memoria del montón se puede ajustar.
  • La "Especificación de máquina virtual de Java" estipula que el montón puede estar en un espacio de memoria físicamente discontinuo, pero lógicamente debe considerarse continuo.
  • Todos los subprocesos comparten el montón de Java, donde también se pueden dividir los búferes privados de subprocesos (Búfer de asignación local de subprocesos, TLAB) .
  • La descripción del montón de Java en la "Especificación de la máquina virtual de Java" es: Todas las instancias y matrices de objetos deben asignarse en el montón en tiempo de ejecución. (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)
  • Es posible que las matrices y los objetos nunca se almacenen en la pila porque el marco de la pila contiene una referencia a la ubicación del objeto o la matriz en el montón.
  • Una vez que finaliza el método, los objetos del montón no se eliminarán inmediatamente, sino que solo se eliminarán durante la recolección de elementos no utilizados.
  • El montón es el área clave para que GC (Garbage Collection, Garbage Collector) realice la recolección de basura.

inserte la descripción de la imagen aquí

6.2 Configuración del tamaño de la memoria del montón y OOM

El área del montón de Java se usa para almacenar instancias de objetos de Java, por lo que el tamaño del montón se ha establecido cuando se inicia la JVM, y puede configurarlo a través de las opciones "-Xmx" y "-Xms".
"-xms" se usa para indicar la memoria de inicio del área del montón, equivalente a -XX: InitialHeapsize "-Xmx" se usa para indicar la memoria máxima del área del montón, equivalente a -XX:MaxHeapsize Una vez que el tamaño de la memoria en
el el área del montón excede Cuando se alcanza la memoria máxima especificada por "-Xmx", se lanzará una excepción OutOfMemoryError.
Por lo general, los dos parámetros -Xms y -Xmx se configuran con el mismo valor. El propósito es mejorar el rendimiento sin volver a particionar el tamaño del almacenamiento dinámico después de que el mecanismo de recolección de elementos no utilizados de Java limpie el almacenamiento dinámico.
De forma predeterminada, el tamaño de la memoria inicial: tamaño de la memoria física de la computadora/64. Tamaño máximo de memoria: tamaño de memoria física de la computadora / 4

/**
 * 1. 设置堆空间大小的参数
 * -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
 *      -X 是jvm的运行参数
 *      ms 是memory start
 * -Xmx 用来设置堆空间(年轻代+老年代)的最大内存大小
 *
 * 2. 默认堆空间的大小
 *    初始内存大小:物理电脑内存大小 / 64
 *             最大内存大小:物理电脑内存大小 / 4
 * 3. 手动设置:-Xms600m -Xmx600m
 *     开发中建议将初始堆内存和最大的堆内存设置成相同的值。
 *
 * 4. 查看设置的参数:方式一: jps   /  jstat -gc 进程id
 *                  方式二:-XX:+PrintGCDetails
 * @author shkstart  [email protected]
 * @create 2020  20:15
 */
public class HeapSpaceInitial {
    public static void main(String[] args) {

        //返回Java虚拟机中的堆内存总量
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        //返回Java虚拟机试图使用的最大堆内存量
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms : " + initialMemory + "M");
        System.out.println("-Xmx : " + maxMemory + "M");

//        System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G");
//        System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G");

        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

-Xmx600M -Xms600M -XX:+ImprimirGCDetalles


¿Por qué es solo 575M cuando está claramente configurado en 600M?
Solo se utiliza una parte del área de supervivencia en la nueva generación, y la otra parte es gratuita.
(179200+409600)/1024=575
(179200+409600+25600)/1024=600


Capacidad total de S0C: área S0 S0U: capacidad utilizada del área S0

Instale el complemento de Visual GC para jvisualvm
Después de abrir jvisualvm, haga clic en Herramientas—>Complementos—>Complementos disponibles---->Encuentre la versión correspondiente de Visual GC.
Se usa la versión JDK8 y el Visual GC correspondiente es 2.1.2 , porque la instalación directa del complemento ya se ha realizado. No, vaya al sitio web oficial para descargar el complemento e instalarlo
https://visualvm.github.io/download.html


Haga clic en el enlace en el cuadro rojo de arriba para saltar a la siguiente interfaz y encontrar su versión de JDK correspondiente 

Encuentre la versión de Visual GC 2.1.2 que desea descargar


Una vez completada la descarga, coloque el paquete de instalación en el directorio bin del directorio de instalación de jdk

 Luego abra jvisualvm para instalar el complemento.

 

 
6.3 Generación Joven y Generación Vieja

Los objetos Java almacenados en la JVM se pueden dividir en dos categorías:
uno es un objeto transitorio de corta duración, que se crea y destruye muy rápidamente,
y el otro tiene un ciclo de vida muy largo. Bajo las circunstancias, también puede ser consistente con el ciclo de vida de la JVM.
Si el área del montón de Java se subdivide aún más, se puede dividir en la generación joven (YoungGen) y la generación anterior (oldGen). La generación joven se puede dividir en el espacio Eden, el espacio Survivor0 y el espaciosurvivor1 (a veces también llamado de al área).

Configure la proporción de la nueva generación y la generación anterior en la estructura del montón. En general, el valor predeterminado se puede usar sin modificarlo deliberadamente. Predeterminado -XX:NewRatio=2, lo que significa que la nueva generación ocupa 1, la generación anterior ocupa 2 y la nueva generación ocupa 1/3 de todo el montón Puede modificar -XX:NewRatio=4, lo que significa que el la nueva generación ocupa 1, la generación anterior ocupa 4 y la nueva generación ocupa
1/3 de todo el montón

En HotSpot, la relación predeterminada entre el espacio de Eden y los otros dos espacios de sobrevivientes es 8: 1: 1. Por supuesto, los desarrolladores pueden ajustar esta relación de espacio a través de la opción "-XX: SurvivorRatio". Por ejemplo -XX:SurvivorRatio=8
Casi todos los objetos de Java se crean recientemente en el área de Eden. La mayoría de los objetos de Java se destruyen en la nueva generación.

La investigación especial de IBM muestra que el 80% de los objetos de la nueva generación son "vivos y muertos".
Puede usar la opción "-Xmn" para establecer el tamaño máximo de memoria de la nueva generación. Este parámetro generalmente usa el valor predeterminado.

-XX:NewRatio: Establezca la relación de la nueva generación con respecto a la generación anterior. El valor predeterminado es 2.
-XX:SurvivorRatio: establezca la proporción del área de Eden con respecto al área de Survivor en la nueva generación. El valor predeterminado es 8
-XX:-UseAdaptiveSizePolicy: desactiva la política de asignación de memoria adaptativa (no se usa temporalmente)
-Xmn: establece la memoria máxima de la nueva generación. (generalmente no establecido)

6.4 Proceso de asignación de objetos gráficos


Una descripción general del proceso de asignación de objetos:

La asignación de memoria para nuevos objetos es una tarea muy rigurosa y compleja. Los diseñadores de JVM no solo deben considerar cómo y dónde asignar la memoria, sino que también deben considerar Gc porque el algoritmo de asignación de memoria está estrechamente relacionado con el algoritmo de recuperación de memoria. Si la fragmentación de memoria ocurrirá en el espacio de la memoria después de que se realice la recuperación de la memoria.
1) Los objetos nuevos se colocan primero en Eden Park. Hay un límite de tamaño para esta zona.
2) Cuando el espacio en Eden está lleno, el programa necesita crear objetos nuevamente, y el recolector de basura de la JVM realizará la recolección de basura (GC menor) en el área de Eden para destruir los objetos en el área de Eden a los que ya no se hace referencia. por otros objetos. Luego cargue nuevos objetos en el área de Eden
3) y luego mueva los objetos restantes en el área de Eden al área de sobrevivientes 0.
4) Si se vuelve a activar la recolección de basura, los que sobrevivieron la última vez se colocarán en el Superviviente 0. Si no se reciclan, se colocarán en el Superviviente 1.
5) Si vuelve a pasar por la recolección de basura, se volverá a colocar en Survivor 0 y luego irá a Survivor 1.
6) ¿Cuándo puedo ir a la residencia de ancianos?Se ​​puede configurar el número de veces. El valor predeterminado es 15 veces. Puede establecer el parámetro: -XX:MaxTenuringThreshold= para la configuración.
7) En el área de cuidado de ancianos, es relativamente tranquila. Cuando la memoria en el área de retiro es insuficiente, GC: Major GC se activa nuevamente para limpiar la memoria en el área de retiro.
8) Si el Distrito de Ancianos ejecuta el GC Mayor y encuentra que el objeto no se puede guardar, ocurrirá una excepción OOM

Resumen:
Resumen para sobrevivientes áreas s0 y s1: hay intercambio después de copiar, el que está vacío es para En cuanto a la
recolección de basura: recolectar frecuentemente en áreas de recién nacidos, rara vez en áreas de ancianos y casi nunca en áreas permanentes/metaespacio.

6.5. GC menor, GC mayor, GC completo

Cuando la JVM está realizando Gc, no recicla las tres áreas de memoria anteriores (nueva generación, área de método de generación anterior) juntas cada vez. La mayoría de las veces, el reciclaje se refiere a la nueva generación.
Para la implementación de HotSpot VM, el Gc en el mismo se divide en dos tipos según el área de reciclaje: uno es recolección parcial (Partial Gc) y el otro es recolección completa (Full GC) Recolección parcial: no es una recolección completa de toda la
recolección de elementos no utilizados del montón de Java. Se divide además en:
Minor GC / Young GC: solo la recolección de elementos no utilizados de la nueva generación (Eden\S0, S1)
Recolección de la generación anterior (Major GO / Old GO): solo la recolección de elementos no utilizados de la generación anterior.
Actualmente, solo el CMS GC tiene el comportamiento de recolectar por separado la generación anterior.
Tenga en cuenta que, en muchos casos, Major GC se confundirá con Full Gc, y es necesario distinguir específicamente si se trata de un reciclaje de generación anterior o un reciclaje completo.
Recolección mixta (Mixed GC): recolecta toda la recolección de basura de nueva generación y parte de la generación anterior. Actualmente, solo G1 GC tiene este comportamiento
Recopilación de almacenamiento dinámico completo (Full GC): Recolección de elementos no utilizados que recopila todo el área de método y almacenamiento dinámico de Java.

Mecanismo de activación de GC de generación joven (GC menor):
cuando el espacio de generación joven es insuficiente, se activará GC menor. La generación joven llena aquí se refiere a que el área de Eden está llena, y el Superviviente lleno no activará GC. (Cada GC menor limpiará la memoria de la generación joven. Cuando el GC menor limpie la memoria en el área Eden, también limpiará la basura de la memoria en el área Survivor). Debido a que la mayoría de los objetos Java tienen las características de la
eternidad , Minor GC es muy frecuente y generalmente reciclado. La velocidad también es más rápida. Esta definición es clara y fácil de entender.
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.

Mecanismo de disparo de GC de generación anterior (GC mayor/GC completo):
se refiere al GC que ocurre en la generación anterior. Cuando los objetos desaparecen de la generación anterior, decimos que ha ocurrido "GC principal" o "GC completo".
Aparece Major GC, muchas veces acompañado de al menos un Minor GC (pero no absoluto, en la estrategia de recolección del colector de barrido Parallel, hay un proceso de selección de estrategia para realizar directamente Major GC).
Es decir, cuando no hay suficiente espacio en la generación anterior, primero intentará activar Minor GC. Si no hay suficiente espacio en el futuro,
la velocidad de activación de Major GC y Major GC generalmente será más de 10 veces más lenta que la de Minor GC, y el tiempo de STW será mayor. Si la memoria no es suficiente después de Major GC, se informará OOM.

Mecanismo de activación de GC completo: ( descrito en detalle más adelante) Hay cinco situaciones que activan la ejecución de GC completo:
(1) Cuando el sistema... 3) Espacio insuficiente en el área del método(4) El tamaño promedio de la generación anterior después de pasar el Minor GC es más grande que la memoria disponible en la generación anterior(5) Al copiar desde el área Eden, el espacio superviviente 0 (Desde el espacio) al área superviviente espacio 1 (ToSpace), el objeto Si el tamaño es mayor que la memoria disponible de Al espacio, el objeto se transfiere a la generación anterior y la memoria disponible de la generación anterior es más pequeña que el tamaño del objeto.Nota: se debe evitar el GC completo en el desarrollo y la optimización. Esto acortará temporalmente el tiempo.




6.6 Idea de generación de espacio de montón

¿Por qué necesita dividir el montón de Jaya en generaciones? ¿No funciona correctamente sin generación? Después de la investigación, diferentes objetos tienen diferentes ciclos de vida. 70%-99% de los objetos son objetos temporales.
La nueva generación: consta de Eden y dos sobrevivientes del mismo tamaño (también conocidos como from/to, s0/s1), y to siempre está vacío.
Generación anterior: almacena objetos que sobreviven a múltiples GC en la nueva generación.

De hecho, es completamente posible no dividir en generaciones.La única razón para la generación es optimizar el rendimiento de cc. Si no hay generación, entonces todos los objetos están juntos, como encerrar a todas las personas en una escuela en un salón de clases. Durante la GC, es necesario encontrar qué objetos son inútiles, de modo que se escaneen todas las áreas del montón. Y muchos objetos nacen y mueren, si se divide en generaciones, coloque el objeto recién creado en un lugar determinado, y cuando el GC, primero recicle el área que almacena los objetos "vividos y muertos", para que sea liberado Sal con mucho espacio.

6.7 Estrategia de asignación de memoria (o reglas de promoción de objetos (Promoción))


Si el objeto nace en Eden y aún sobrevive después del primer MinorGC, y el sobreviviente puede acomodarlo, se moverá al espacio de sobreviviente y la edad del objeto se establecerá en 1. Cada vez que un objeto sobrevive a un Minor GC en el área de sobrevivientes, su edad aumentará en 1 año. Cuando su edad aumenta a un cierto nivel (el valor predeterminado es 15 años, de hecho, cada JVM y cada GC son diferentes), será eliminado Promovido a la generación anterior.

El umbral de edad para promocionar un objeto a la generación anterior se puede establecer mediante la opción -XX:MaxTenuringThreshold.

Los principios de asignación de objetos para diferentes grupos de edad son los siguientes: Priorice la asignación de
objetos grandes a Eden y asígnelos directamente a la generación anterior.
Intente evitar demasiados objetos grandes en el programa. Los objetos de larga vida se asignan a la generación anterior.

Juicio dinámico de la edad del objeto
Si la suma del tamaño de todos los objetos de la misma edad en el área de supervivientes es mayor que la mitad del espacio de supervivientes, los objetos cuya edad sea mayor o igual a esta edad pueden entrar directamente en la vejez sin esperar la edad requerida en MaxTenuringThreshold.

Garantía de asignación de espacio, -XX:HandlePromotionFailure

6.8 Asignación de memoria para objetos: TLAB

¿Por qué hay TLAB ((Búfer de asignación local de subprocesos)?
El área de almacenamiento dinámico es un área compartida de subprocesos, y cualquier subproceso puede acceder a los datos compartidos en el área de almacenamiento dinámico.
Dado que la creación de instancias de objetos es muy frecuente en la JVM, de forma concurrente entorno, desde el área del montón Dividir el espacio de la memoria no es seguro para subprocesos
Para evitar que varios subprocesos operen en la misma dirección, es necesario utilizar mecanismos como el bloqueo, lo que afectará la velocidad de asignación.

¿Qué es TLAB?
Desde la perspectiva del modelo de memoria en lugar de la recolección de basura, el área de Eden continúa dividiéndose.La JVM asigna un área de caché privada para cada subproceso, que se incluye en el espacio de Eden.
Cuando varios subprocesos asignan memoria al mismo tiempo, el uso de TLAB puede evitar una serie de problemas no relacionados con la seguridad de subprocesos
y también puede mejorar el rendimiento de la asignación de memoria, por lo que podemos llamar a este método de asignación de memoria una estrategia de asignación rápida.
Hasta donde yo sé, todas las JVM derivadas de openJDK proporcionan un diseño TLAB.

Nueva explicación de TLAB:
aunque no todas las instancias de objetos pueden asignar memoria en TLAB con éxito, JVM usa TLAB como la primera opción para la asignación de memoria.
En el programa, los desarrolladores pueden establecer si habilitar el espacio TLAB a través de la opción "-xX:UseTLAB".
Por defecto, la memoria en el espacio TLAB es muy pequeña, ocupando solo el 1% de todo el espacio Eden.Por supuesto, podemos establecer el porcentaje del espacio Eden ocupado por el espacio TLAB a través de la opción "-XX:TABwasteTargetPercent".
Una vez que el objeto no puede asignar memoria en el espacio TLAB, la JVM intentará usar el mecanismo de bloqueo para garantizar la atomicidad de las operaciones de datos, asignando así memoria directamente en el espacio Eden.

6.9 Resumen de la configuración de parámetros del espacio de almacenamiento dinámico

Parámetros jvm de uso común para probar el espacio de almacenamiento dinámico:
-XX:+PrintFlagsInitial: ver los valores iniciales predeterminados de todos los parámetros
-XX:+PrintFlagsFinal: ver los valores finales de todos los parámetros (puede haber modificaciones, ya no valores iniciales )

Vea específicamente el comando de un parámetro: jps: vea el proceso que se está ejecutando actualmente
jinfo -flag SurvivorRatio process id

-Xms: memoria de espacio de pila inicial (1/64 de memoria física de forma predeterminada)
-Xmx: memoria de espacio de pila máxima (1/4 de memoria física de forma predeterminada)
-Xmn: establece el tamaño de la nueva generación. (Valor inicial y valor máximo)
-XX:NewRatio: Configure la proporción de la nueva generación y la generación anterior en la estructura del montón
-XX:SurvivorRatio: Establezca la proporción del espacio Eden y S0/S1 en la nueva generación
-XX:MaxTenuringThreshold : Establecer la basura de nueva generación Edad máxima de
-XX:+PrintGCDetails: Salida detallada del registro de procesamiento de GC
Imprimir información breve de gc: ① -XX:+PrintGC ② -verbose:gc

-XX:HandlePromotionFailure: Establecer la garantía de asignación de espacio

Antes de que ocurra Minor GC, la máquina virtual verifica si el espacio continuo máximo disponible en la generación anterior es mayor que el espacio total de todos los objetos en la nueva generación.
Si es más grande, el GC menor es seguro esta vez
Si es más pequeño, la máquina virtual verifica si el valor de configuración -xX:HandlePromotionFailure permite fallas de garantía.
Si HandlePromotionFailure=true, seguirá comprobando si el espacio continuo máximo disponible en la generación anterior es mayor que el tamaño medio de los objetos promovidos a la generación anterior.
Si es mayor, intente realizar un GC menor, pero este GC menor sigue siendo riesgoso;
si es menor, realice un GC completo en su lugar.
Si HandlePromotionFailure=false, realice un GC completo en su lugar.
Después de la actualización 24 de JDK6, el parámetro HandlePromotionFailure ya no afectará la política de garantía de asignación de espacio de la máquina virtual. Observe los cambios en el código fuente en openJDK. Aunque el parámetro HandlePromotionFailure está definido en el código fuente, ya no se usará en el código. La regla después de la Actualización 24 de JDK6 se convierte en que se realizará una GC menor siempre que el espacio continuo de la generación anterior sea mayor que el tamaño total de los objetos en la nueva generación o el tamaño promedio de las promociones anteriores; de lo contrario, se realizará una GC completa.

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

En "Comprensión profunda de la máquina virtual de Java", hay una descripción de este tipo sobre la memoria de almacenamiento dinámico de Java:
Con el desarrollo del período de compilación JIT y la madurez gradual de la tecnología de análisis de escape, la tecnología de optimización de reemplazo escalar y asignación en la pila conducirá a algunos cambios sutiles Los objetos se asignan en el montón y gradualmente se vuelven menos "absolutos" .
En la máquina virtual de Java, a los objetos se les asigna memoria en el montón de Java, lo cual es de conocimiento común. Sin embargo, hay un caso especial, es decir, si después del análisis de escape (Escape Analysis), se encuentra que un objeto no escapa al método, entonces puede optimizarse para ser ubicado en la pila . Esto elimina la necesidad de asignar memoria en el montón y la recolección de elementos no utilizados. Esta es también la técnica de almacenamiento fuera del montón más común.
Además, el TaoBaoVM antes mencionado basado en la profunda personalización de openJDK, en el que la innovadora tecnología GCIH (GC invisible heap) implementa fuera del montón, mueve objetos Java con un largo ciclo de vida del montón al exterior del montón, y cc no puede administrar las partes internas de GCIH El objeto Java, para lograr el propósito de reducir la frecuencia de reciclaje de cc y mejorar la eficiencia de reciclaje de Gc.

Descripción general del análisis de escape
La forma de asignar objetos en el montón a la pila requiere el uso del análisis de escape.
Se trata de un algoritmo de análisis de flujo de datos global de funciones cruzadas que puede reducir eficazmente la carga de sincronización y la presión de asignación de almacenamiento dinámico de memoria en los programas Java.
A través del análisis de escape, el compilador Java Hotspot puede analizar el ámbito de uso de una nueva referencia de objeto y decidir si asignar este objeto en el montón.
El comportamiento básico del análisis de escape es analizar el alcance dinámico de los objetos:
cuando un objeto se define en un método y el objeto solo se usa dentro del método, se considera que no se ha producido ningún escape.
Cuando un objeto se define en un método y un método externo hace referencia a él, se considera que se ha escapado. Por ejemplo, como un parámetro de llamada pasado a otros lugares.

Los objetos que no se han escapado se pueden asignar a la pila y el espacio de la pila se elimina cuando finaliza la ejecución del método.

/**
 * 逃逸分析
 *
 *  如何快速的判断是否发生了逃逸分析,大家就看new的对象实体是否有可能在方法外被调用。
 * @author shkstart
 * @create 2020 下午 4:00
 */
public class EscapeAnalysis {

    public EscapeAnalysis obj;

    /*
    方法返回EscapeAnalysis对象,发生逃逸
     */
    public EscapeAnalysis getInstance(){
        return obj == null? new EscapeAnalysis() : obj;
    }
    /*
    为成员属性赋值,发生逃逸
     */
    public void setObj(){
        this.obj = new EscapeAnalysis();
    }
    //思考:如果当前的obj引用声明为static的?仍然会发生逃逸。

    /*
    对象的作用域仅在当前方法中有效,没有发生逃逸
     */
    public void useEscapeAnalysis(){
        EscapeAnalysis e = new EscapeAnalysis();
    }
    /*
    引用成员变量的值,发生逃逸
     */
    public void useEscapeAnalysis1(){
        EscapeAnalysis e = getInstance();
        //getInstance().xxx()同样会发生逃逸
    }
}

Configuración de parámetros:
después de la versión JDK 6u23, el análisis de escape se ha habilitado de forma predeterminada en HotSpot. .Si está utilizando una versión anterior, los desarrolladores pueden
habilitar explícitamente el análisis de escape mediante: la opción "-xX: +DoEscapeAnalysis"
y ver los resultados del filtro del análisis de escape mediante la opción "-XX:+PrintEscapeAnalysis".

Conclusión:
si las variables locales se pueden usar en el desarrollo, no las use definidas fuera del método.

Mediante el análisis de escape, el compilador puede optimizar el código de la siguiente manera:
1) Asignación en la pila . Convierta la asignación de almacenamiento dinámico en asignación de pila. Si se asigna un objeto en una subrutina, el objeto puede ser un candidato para la asignación de pila en lugar de la asignación de montón si nunca se escapa un puntero al objeto.
2) Se omite la sincronización . Si se encuentra que un objeto es accesible solo desde un subproceso, es posible que las operaciones en ese objeto no se sincronicen.
3), objetos separados o reemplazo escalar . Se puede acceder a algunos objetos sin que existan como una estructura de memoria continua, por lo que una parte (o la totalidad) del objeto puede no almacenarse en la memoria, sino almacenarse en los registros de la CPU.

Asignación de pila para optimización de código

De acuerdo con los resultados del análisis de escape durante la compilación, el compilador JIT encuentra que si un objeto no escapa al método, puede optimizarse para ubicarse en la pila. Una vez completada la asignación, continúe ejecutándose en la pila de llamadas y, finalmente, finaliza el subproceso, se reclama el espacio de la pila y también se reclama el objeto de la variable local. Esto elimina la necesidad de recolección de basura.

Los escenarios comunes de asignación de pilas
se han explicado en el análisis de escape. Los escenarios en los que se produce el escape son la asignación de valores a las variables miembro, los valores devueltos del método y el paso de referencias de instancia.

Omisión de sincronización para optimización de código (eliminación de bloqueo)

El costo de la sincronización de subprocesos es bastante alto y la consecuencia de la sincronización es reducir la concurrencia y el rendimiento.
Al compilar dinámicamente un bloque de sincronización, el compilador JIT puede usar el análisis de escape para determinar si solo un subproceso puede acceder al objeto de bloqueo utilizado por el bloque de sincronización y si no se ha liberado a otros subprocesos. De lo contrario, el compilador JIT cancelará la sincronización de esta parte del código al compilar el bloque de sincronización. Esto puede mejorar en gran medida la simultaneidad y el rendimiento. Este proceso de cancelación de sincronización se denomina elisión de sincronización, también denominada eliminación de bloqueo.

Reemplazo escalar para optimización de código
Escalar (Scalar) se refiere a 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 agregados, y los objetos en Java son agregados porque se pueden descomponer en otros agregados y escalares.
En la etapa JIT, si después del análisis de escape se encuentra que el mundo exterior no accederá a un objeto, luego de la optimización JIT, el objeto se desarmará en varias variables miembro contenidas en él para reemplazarlo. Este proceso se conoce como sustitución escalar.
Configuración de parámetros de reemplazo escalar:
Parámetro -XX:+Eliminar asignaciones: Habilita el reemplazo escalar (abierto de forma predeterminada), lo que permite que los objetos se dispersen y asignen en la pila.

El código anterior realiza 100 millones de asignaciones en la función principal. Llamada para crear un objeto Dado que la instancia del objeto Usuario necesita ocupar unos 16 bytes de espacio, el espacio asignado acumulativo alcanza casi 1,5 GB. Si el espacio de almacenamiento dinámico es menor que este valor, inevitablemente se producirá GC. Ejecute el código anterior con los siguientes parámetros:
-server -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+Printcc -XX:+EliminateAl1Gcations
Los parámetros utilizados aquí son los siguientes:
Parámetros -servidor: inicie el modo servidor, ya que puede solo se habilitará en el análisis de escape del modo servidor. · Parámetro -XX:+DoEscapeAnalysis: habilitar el análisis de escape
Parámetro -Xmx10m: especifica el espacio de almacenamiento dinámico máximo de 10 MB
Parámetro -XX:+PrintGC: se imprimirá el registro Gc.
Parámetro -XX:+Eliminar asignaciones: permite el reemplazo escalar (abierto de forma predeterminada), lo que permite que los objetos se dispersen y se asignen en la pila. Por ejemplo, si un objeto tiene dos campos de id y nombre, estos dos campos se considerarán como dos Se asignan variables locales independientes.

La asignación en la pila no está habilitada en HotSpot, pero el reemplazo escalar está habilitado. El análisis de escape también está habilitado de forma predeterminada.

 7. Área de método

7.1 Interacción entre pila, montón y área de método

Preguntas de la entrevista de Dachang
Hablemos del modelo de memoria JVM. ¿Cuáles son las áreas? ¿Qué hacen? La
generación de memoria de Java8 mejora
qué áreas están en la memoria JVM y cuál es el papel de cada área. ¿
Distribución de memoria JVM/estructura de memoria? Stack y ¿La diferencia entre el montón? ¿La estructura del montón? ¿Por qué dos áreas supervivientes?
La proporción de Eden y el superviviente asigna particiones de memoria jvm, ¿por qué particiones de memoria Java de nueva generación y antiguas?
hay La estructura de memoria de la máquina virtual, la proporción del Edén y sobreviviente. ¿Por qué la memoria de la máquina virtual debe dividirse en nueva generación, generación anterior y generación permanente? ¿Por qué la nueva generación se divide en Edén y sobreviviente? El modelo de memoria y las particiones de JVM deben detallar qué poner en cada área. El modelo de memoria de la JVM, ¿qué cambios se han realizado en Java 8 , en qué áreas se divide la memoria de la JVM y cuál es la función de cada área? ¿ Se producirá la recolección de basura en la generación permanente de la jvm de asignación de memoria de Java? ¿generación?










7.2 Comprensión del área del método

La "Especificación de máquina virtual de Java" establece claramente: "Aunque todas las áreas de métodos son lógicamente parte del montón, es posible que algunas implementaciones simples no opten por realizar la recolección de elementos no utilizados o la compresión". -Heap (non-heap), la finalidad es separarlo del montón.
Por lo tanto, el área de métodos se considera un espacio de memoria independiente del montón de Java.


Method Area (Área de método), como el montón de Java, es un área de memoria compartida por cada subproceso.
El área de método se crea cuando se inicia la JVM, y su espacio de memoria física real puede ser discontinuo al igual que el área de almacenamiento dinámico de Java.
El tamaño del área de método, como el espacio de almacenamiento dinámico, puede ser fijo o ampliable. El tamaño del área de método determina cuántas clases puede guardar el sistema. Si el sistema define demasiadas clases, lo que hace que el área de método se desborde, el la máquina también arrojará un error de falta de memoria: java.lang.outOfMemoryError: PermGen spacel o java.lang.outOfMemoryError: Metaspace. Cargue una gran cantidad de paquetes jar de terceros; Tomcat implementa demasiados proyectos (30-50); se genera una gran cantidad de clases de reflexión dinámica
. Cerrar la JVM liberará la memoria en esta área.

La evolución del área de método en Hotspot
En jdk7 y antes, se acostumbra llamar al área de método generación permanente. A partir de jdk8, la generación permanente se reemplaza por el metaespacio.
En JDK 8, los metadatos de las clases ahora se almacenan en el montón nativo y este espacio se llama Metaspace.
Esencialmente, el área de método y la generación permanente no son equivalentes. Solo para punto de acceso. La "Especificación de máquina virtual de Java" no establece requisitos uniformes sobre cómo implementar el área de método. Por ejemplo: El concepto de generación permanente no existe en BEA JRockit/IBM J9.
Mirándolo ahora, no era una buena idea usar la generación permanente en ese entonces. Hace que los programas Java sean más propensos a ooM (superando el límite superior de -XX:MaxPermSize)

En JDK 8, el concepto de generación permanente finalmente se abandonó por completo, y en su lugar se utilizó el metaespacio (Metaspace) implementado en la memoria local como JRockit y J9.

La esencia del metaespacio es similar a la de la generación permanente, que es la implementación del área de método en la especificación JVM. Sin embargo, la mayor diferencia entre el metaespacio y la generación permanente es que el metaespacio no está en la memoria configurada por la máquina virtual,
sino que usa la memoria local.
La generación permanente y el metaespacio no solo cambian de nombre, sino que también se ajusta la estructura interna.
De acuerdo con la "Especificación de máquina virtual de Java", si el área de método no puede cumplir con los nuevos requisitos de asignación de memoria, se generará una excepción OOM.

7.3 Establecer el tamaño del área del método y OOM

jdk8 y posteriores:
el tamaño del área de metadatos se puede especificar con los parámetros -XX:MetaspaceSize y -XX:MaxMetaspaceSize.

El valor predeterminado depende de la plataforma. En Windows, -XX:Metaspacesize es 21M, y el valor de -XX:MaxMetaspaceSize es -1, es decir, no hay límite

A diferencia de la generación permanente, si no especifica un tamaño, la máquina virtual usará por defecto toda la memoria disponible del sistema. Si el área de metadatos se desborda, la máquina virtual también generará una excepción OutOfMemoryError: Metaspace
-XX:MetaspaceSize: establece el tamaño inicial del metaespacio. Para una JVM del lado del servidor de 64 bits, el valor predeterminado -XX:Metaspacesize es 21 MB. Esta es la marca de agua alta inicial. Una vez que se toca esta marca de agua, se activará Full Gc y descargará las clases inútiles (es decir, los cargadores de clases correspondientes a estas clases ya no están activos), y luego se restablecerá la marca de agua alta. . El valor de la nueva marca de límite superior depende de cuánto metaespacio se libere después de GC. Si el espacio liberado no es suficiente, aumente el valor adecuadamente si no supera MaxMetaspaceSize. Si hay demasiado espacio para liberar, reduzca este valor adecuadamente.
Si la marca de límite superior inicial se establece demasiado baja, el ajuste de la marca de límite superior descrito anteriormente puede ocurrir muchas veces. A través del registro del recolector de basura, se puede observar que el GC completo se llama varias veces. Para evitar Gc frecuentes, se recomienda establecer -XX:MetaspaceSize en un valor relativamente alto.

7.4 Estructura interna del área de método

 

 Conjunto de constantes de tiempo de ejecución y conjunto de constantes

7.5 Detalle de la evolución del área de método

 Las entidades de objeto correspondientes a referencias estáticas siempre tienen espacio de almacenamiento dinámico.

 7.6 Recolección de Basura en el Área de Método


Resumir

8. Creación de instancias de objetos, diseño de memoria y posicionamiento de acceso, memoria directa

Preguntas de la entrevista de Dachang
¿Cómo se almacenan los objetos en la JVM?, ¿Qué hay en la información del encabezado del objeto?, ¿
Qué hay en el encabezado del objeto Java?

8.1 Ejemplificación de objetos 

Pasos de creación de instancias de objetos:

①Cuando la máquina virtual encuentra una nueva instrucción, primero verifica si el parámetro de esta instrucción puede ubicar una referencia simbólica de una clase en el grupo constante de Metaspace y verifica si la clase representada por la referencia simbólica se ha cargado, analizado y inicializado ( Es decir, para determinar si existe la información del clasificador ). De lo contrario, en el modo de delegación principal, utilice el cargador de clases actual para buscar el archivo .class correspondiente con ClassLoader+nombre del paquete+clave del nombre de la clase. Si no se encuentra el archivo, se lanza una excepción ClassNotFoundException y, si se encuentra, se carga la clase y se genera el objeto de clase Class correspondiente.

② Primero calcule el tamaño del espacio ocupado por el objeto y luego divida una parte de la memoria en el montón para el nuevo objeto.
Si la variable miembro de la instancia es una variable de referencia, solo se asigna el espacio de la variable de referencia, que tiene un tamaño de 4 bytes.

Si la memoria es normal, la máquina virtual utilizará el método de colisión de punteros (Bump The Pointer) para asignar memoria para el objeto .
Significa que toda la memoria utilizada está en un lado, la memoria libre está en el otro lado y un puntero se coloca en el medio como indicador del punto de demarcación. Asignar memoria es simplemente mover el puntero hacia el lado libre una distancia igual al tamaño del objeto. Si el recolector de basura elige un algoritmo de compresión basado en Serial o ParNew, la máquina virtual adopta este método de asignación. Generalmente, cuando se utiliza un colector con un proceso compacto (acabado), se utilizan colisiones de punteros.

如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表法来为对象分配内存
意思是虚拟机维护了一个列表,记录上哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式成为“空闲列表(Free List ) ".

⑤选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

⑥将对象的所属类(即类的元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于JVM实现。

⑦在Java程序的视角看来,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。因此一般来说(由字节码中是否跟随有invokespecial指令所决定),new指令之后会接着就是执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全创建出来。

8.2. 对象的内存布局

/**
 * 测试对象实例化的过程
 *  ① 加载类元信息 - ② 为对象分配内存 - ③ 处理并发问题  - ④ 属性的默认初始化(零值初始化)
 *  - ⑤ 设置对象头的信息 - ⑥ 属性的显式初始化、代码块中初始化、构造器中初始化
 *
 *  给对象的属性赋值的操作:
 *  ① 属性的默认初始化 - ② 显式初始化 / ③ 代码块中初始化 - ④ 构造器中初始化
 * @author shkstart  [email protected]
 * @create 2020  17:58
 */

 

public class Customer{
    int id = 1001;
    String name;
    Account acct;

    {
        name = "匿名客户";
    }
    public Customer(){
        acct = new Account();
    }

}
class Account{

}

public class CustomerTest {
    public static void main(String[] args) {
        Customer cust = new Customer();
    }
}

CustomerTest 的main方法发生了什么?

8.3. 对象的访问定位

句柄访问方式: 

直接指针方式:

句柄访问与直接指针访问的优劣:

这两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference本身不需要被修改。

使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访 问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本,HotSpot主要使用第二种方式进行对象访问。

8.4. 直接内存

九.JVM之执行引擎

9.1.执行引擎概述

La compilación de archivos Java en archivos de clase se denomina compilación frontal (javac) , y la compilación de bytecodes en instrucciones de máquina en el motor de ejecución se denomina compilación de fondo .


 

9.2 El proceso de compilación y ejecución de código Java

 

 

 
9.3 Código máquina, instrucciones, lenguaje ensamblador

El lenguaje de alto nivel debe traducirse primero a lenguaje ensamblador, y luego el lenguaje ensamblador se traduce a instrucciones de máquina.

9.4 Intérprete

9.5 Compilador JIT

 

 

Diez. StringTable

10.1 Propiedades básicas de String

10.2 Asignación de memoria de cadenas

Utilice el método interno de String para agregar la cadena al conjunto de constantes de cadena. Al mismo tiempo, este método se puede usar para probar que el conjunto de constantes de cadena en JDK8 se almacena en el montón.

/**
 * jdk6中:
 * -XX:PermSize=6m -XX:MaxPermSize=6m -Xms6m -Xmx6m
 *
 * jdk8中:
 * -XX:MetaspaceSize=6m -XX:MaxMetaspaceSize=6m -Xms6m -Xmx6m
 * @author shkstart  [email protected]
 * @create 2020  0:36
 */
public class StringTest3 {
    public static void main(String[] args) {
        //使用Set保持着常量池引用,避免full gc回收常量池行为
        Set<String> set = new HashSet<String>();
        //在short可以取值的范围内足以让6MB的PermSize或heap产生OOM了。
        short i = 0;
        while(true){
            set.add(String.valueOf(i++).intern());
        }
    }
}



 


10.3 Operaciones

10.4 Operación de concatenación de cadenas

Los detalles de ejecución de s1 + s2 son los siguientes: (la variable s está definida temporalmente por mí)
① StringBuilder s = new StringBuilder();
② s.append(“a”)
③ s.append(“b”)
④ s .toString( ) --> aproximadamente igual a new String("ab")

Suplemento: StringBuilder se usa después de jdk5.0, y StringBuffer se usa antes de jdk5.0
1) ¡La operación de empalme de cadenas no necesariamente usa StringBuilder!
   Si los lados izquierdo y derecho del símbolo de empalme son constantes de cadena o referencias constantes, entonces siga usando optimización en tiempo de compilación, es decir, una forma no StringBuilder.
2) Para la estructura de clases modificadas finales, métodos, tipos de datos básicos y tipos de datos de referencia, se recomienda usar final cuando se pueda usar.
 

package com.atguigu.java1;

import org.junit.Test;

/**
 * 字符串拼接操作
 * @author shkstart  [email protected]
 * @create 2020  0:59
 */
public class StringTest5 {
    @Test
    public void test1(){
        String s1 = "a" + "b" + "c";//编译期优化:等同于"abc"
        String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2
        /*
         * 最终.java编译成.class,再执行.class
         * String s1 = "abc";
         * String s2 = "abc"
         */
        System.out.println(s1 == s2); //true
        System.out.println(s1.equals(s2)); //true
    }

    @Test
    public void test2(){
        String s1 = "javaEE";
        String s2 = "hadoop";

        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";//编译期优化
        //如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结果:javaEEhadoop
        String s5 = s1 + "hadoop";
        String s6 = "javaEE" + s2;
        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
        //intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中javaEEhadoop的地址;
        //如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回次对象的地址。
        String s8 = s6.intern();
        System.out.println(s3 == s8);//true
    }

    @Test
    public void test3(){
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        /*
        如下的s1 + s2 的执行细节:(变量s是我临时定义的)
        ① StringBuilder s = new StringBuilder();
        ② s.append("a")
        ③ s.append("b")
        ④ s.toString()  --> 约等于 new String("ab")

        补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
         */
        String s4 = s1 + s2;//
        System.out.println(s3 == s4);//false
    }
    /*
    1. 字符串拼接操作不一定使用的是StringBuilder!
       如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
    2. 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
     */
    @Test
    public void test4(){
        final String s1 = "a";
        final String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);//true
    }
    //练习:
    @Test
    public void test5(){
        String s1 = "javaEEhadoop";
        String s2 = "javaEE";
        String s3 = s2 + "hadoop";
        System.out.println(s1 == s3);//false

        final String s4 = "javaEE";//s4:常量
        String s5 = s4 + "hadoop";
        System.out.println(s1 == s5);//true

    }

    /*
    体会执行效率:通过StringBuilder的append()的方式添加字符串的效率要远高于使用String的字符串拼接方式!
    详情:① StringBuilder的append()的方式:自始至终中只创建过一个StringBuilder的对象
          使用String的字符串拼接方式:创建过多个StringBuilder和String的对象
         ② 使用String的字符串拼接方式:内存中由于创建了较多的StringBuilder和String的对象,内存占用更大;如果进行GC,需要花费额外的时间。

     改进的空间:在实际开发中,如果基本确定要前前后后添加的字符串长度不高于某个限定值highLevel的情况下,建议使用构造器实例化:
               StringBuilder s = new StringBuilder(highLevel);//new char[highLevel]
     */
    @Test
    public void test6(){

        long start = System.currentTimeMillis();

//        method1(100000);//4014
        method2(100000);//7

        long end = System.currentTimeMillis();

        System.out.println("花费的时间为:" + (end - start));
    }

    public void method1(int highLevel){
        String src = "";
        for(int i = 0;i < highLevel;i++){
            src = src + "a";//每次循环都会创建一个StringBuilder、String
        }
//        System.out.println(src);

    }

    public void method2(int highLevel){
        //只需要创建一个StringBuilder
        StringBuilder src = new StringBuilder();
        for (int i = 0; i < highLevel; i++) {
            src.append("a");
        }
//        System.out.println(src);
    }
}



10.5 Uso de interno()

/**
 * 题目:
 * new String("ab")会创建几个对象?看字节码,就知道是两个。
 *     一个对象是:new关键字在堆空间创建的
 *     另一个对象是:字符串常量池中的对象"ab"。 字节码指令:ldc
 *
 * 思考:
 * new String("a") + new String("b")呢?
 *  对象1:new StringBuilder()
 *  对象2: new String("a")
 *  对象3: 常量池中的"a"
 *  对象4: new String("b")
 *  对象5: 常量池中的"b"
 *
 *  深入剖析: StringBuilder的toString():
 *      对象6 :new String("ab")
 *       强调一下,toString()的调用,在字符串常量池中,没有生成"ab"
 *
 * @author shkstart  [email protected]
 * @create 2020  20:38
 */
public class StringNewTest {
    public static void main(String[] args) {
//        String str = new String("ab");

        String str = new String("a") + new String("b");
    }
}

/**
 * 如何保证变量s指向的是字符串常量池中的数据呢?
 * 有两种方式:
 * 方式一: String s = "shkstart";//字面量定义的方式
 * 方式二: 调用intern()
 *         String s = new String("shkstart").intern();
 *         String s = new StringBuilder("shkstart").toString().intern();
 *
 * @author shkstart  [email protected]
 * @create 2020  18:49
 */
public class StringIntern {
    public static void main(String[] args) {

        String s = new String("1");
        s.intern();//调用此方法之前,字符串常量池中已经存在了"1"
        String s2 = "1";
        System.out.println(s == s2);//jdk6:false   jdk7/8:false


        String s3 = new String("1") + new String("1");//s3变量记录的地址为:new String("11")
        //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
        s3.intern();//在字符串常量池中生成"11"。如何理解:jdk6:创建了一个新的对象"11",也就有新的地址。
                                            //         jdk7:此时常量中并没有创建"11",而是创建一个指向堆空间中new String("11")的地址
        String s4 = "11";//s4变量记录的地址:使用的是上一行代码代码执行时,在常量池中生成的"11"的地址
        System.out.println(s3 == s4);//jdk6:false  jdk7/8:true
    }
}

public class StringIntern1 {
    public static void main(String[] args) {
        //StringIntern.java中练习的拓展:
        String s3 = new String("1") + new String("1");//new String("11")
        //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
        String s4 = "11";//在字符串常量池中生成对象"11"
        String s5 = s3.intern();
        System.out.println(s3 == s4);//false
        System.out.println(s5 == s4);//true
    }
}

/**
 * @author shkstart  [email protected]
 * @create 2020  20:17
 */
public class StringExer1 {
    public static void main(String[] args) {
        String s = new String("a") + new String("b");//new String("ab")
        //在上一行代码执行完以后,字符串常量池中并没有"ab"

        String s2 = s.intern();//jdk6中:在串池中创建一个字符串"ab"
                               //jdk8中:串池中没有创建字符串"ab",而是创建一个引用,指向new String("ab"),将此引用返回

        System.out.println(s2 == "ab");//jdk6:true  jdk8:true
        System.out.println(s == "ab");//jdk6:false  jdk8:true
    }
}

/**
 *
 * @author shkstart  [email protected]
 * @create 2020  20:26
 */
public class StringExer2 {
    public static void main(String[] args) {
        String s1 = new String("ab");//执行完以后,会在字符串常量池中会生成"ab"                         // false
//        String s1 = new String("a") + new String("b");执行完以后,不会在字符串常量池中会生成"ab"  // true
        s1.intern();
        String s2 = "ab";
        System.out.println(s1 == s2);
    }
}

Prueba de eficiencia interna
Conclusión: para una gran cantidad de cadenas en el programa, especialmente cuando hay muchas cadenas repetidas, usar intern() puede ahorrar espacio en la memoria.

/**
 * 使用intern()测试执行效率:空间使用上
 *
 * 结论:对于程序中大量存在存在的字符串,尤其其中存在很多重复字符串时,使用intern()可以节省内存空间。
 *
 *
 * @author shkstart  [email protected]
 * @create 2020  21:17
 */
public class StringIntern2 {
    static final int MAX_COUNT = 1000 * 10000;
    static final String[] arr = new String[MAX_COUNT];

    public static void main(String[] args) {
        Integer[] data = new Integer[]{1,2,3,4,5,6,7,8,9,10};

        long start = System.currentTimeMillis();
        for (int i = 0; i < MAX_COUNT; i++) {
//            arr[i] = new String(String.valueOf(data[i % data.length]));
            arr[i] = new String(String.valueOf(data[i % data.length])).intern();

        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为:" + (end - start));

        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.gc();
    }
}

10.6 Deduplicación de cadenas en G1


 

11. Conceptos relacionados con la recolección de basura

11.1 Comprensión de System.gc()

 11.2 Desbordamiento de memoria y pérdida de memoria

11.3. Parar el mundo

 
11.4 Concurrencia y Paralelismo

11.5 Descripción de punto seguro y zona segura

11.6 Referencias fuertes, referencias blandas, referencias débiles, referencias fantasma

fuerte referencia

  

referencia suave

SoftReference userSoftRef = new SoftReference(new User(1, “songhk”));
//La línea de código anterior es equivalente a las siguientes tres líneas de código
User u1 = new User(1, “songhk”);
SoftReference userSoftRef = new SoftReference (u1);
u1 = null;//Eliminar referencia fuerte

**
 * 软引用的测试:内存不足即回收
 *
 * @author shkstart  [email protected]
 * @create 2020  16:06
 */
public class SoftReferenceTest {
    public static class User {
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int id;
        public String name;

        @Override
        public String toString() {
            return "[id=" + id + ", name=" + name + "] ";
        }
    }

    public static void main(String[] args) {
        //创建对象,建立软引用
//        SoftReference<User> userSoftRef = new SoftReference<User>(new User(1, "songhk"));
        //上面的一行代码,等价于如下的三行代码
        User u1 = new User(1,"songhk");
        SoftReference<User> userSoftRef = new SoftReference<User>(u1);
        u1 = null;//取消强引用


        //从软引用中重新获得强引用对象
        System.out.println(userSoftRef.get());

        System.gc();
        System.out.println("After GC:");
//        //垃圾回收之后获得软引用中的对象
        System.out.println(userSoftRef.get());//由于堆空间内存足够,所有不会回收软引用的可达对象。
//
        try {
            //让系统认为内存资源紧张、不够
//            byte[] b = new byte[1024 * 1024 * 7];
            byte[] b = new byte[1024 * 7168 - 635 * 1024];
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            //再次从软引用中获取数据
            System.out.println(userSoftRef.get());//在报OOM之前,垃圾回收器会回收软引用的可达对象。
        }
    }
}

cita débil

La diferencia entre referencias débiles y referencias blandas: las referencias débiles se reciclan cuando se produce GC y las referencias blandas se reciclan cuando no hay memoria suficiente. La aparición de GC no significa que no haya suficiente memoria, y debe pasar la verificación del algoritmo.

referencia fantasma

12. Algoritmo de recolección de basura

1. Descripción general de la recolección de basura


1.1 ¿Qué es la basura?

 

1.2 Por qué se necesita GC


1.3 Mecanismo de recolección de basura de Java

2. Algoritmo de recolección de basura


2.1 Algoritmo de marcado de basura


2.1.1 Conteo de referencia

 

 
2.1.2 Algoritmo de análisis de accesibilidad


 Mecanismo de finalización de objetos

Este método es muy insípido y apenas se usa en el trabajo. 

GC Roots Trazabilidad de MAT y JProfiler

MAT
MAT es la abreviatura de Memory Analyzer, es un potente analizador de memoria en montón de Java. Se utiliza para encontrar fugas de memoria y ver el consumo de memoria.
MAT está desarrollado en base a Eclipse y es una herramienta gratuita de análisis de rendimiento

public class GCRootsTest {
    public static void main(String[] args) {
        List<Object> numList = new ArrayList<>();
        Date birth = new Date();

        for (int i = 0; i < 100; i++) {
            numList.add(String.valueOf(i));
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("数据添加完毕,请操作:");
        new Scanner(System.in).next();
        numList = null;
        birth = null;

        System.out.println("numList、birth已置空,请操作:");
        new Scanner(System.in).next();

        System.out.println("结束");
    }
}

Genere un archivo de volcado de pila
jmap -dump:format=b,file=C:\Users\qinsshuoyu\Desktop\1.hprof.analysis\log\1.hprof 19420

Antes de que numList y birth estén vacíos, hay objetos ArrayList y objetos Date en GC ROOTS


Después de que numList y birth estén vacíos, no hay objetos ArrayList ni objetos Date en GC ROOTS

La clasificación de GC ROOTS en MAT es ligeramente diferente de lo que dije antes, siempre que se divida en clases de sistema, JNI Global, subprocesos y monitores.

2.2 Principio del algoritmo Mark-clear y sus ventajas y desventajas

fase de eliminación de basura


 Los objetos marcados son accesibles, porque solo los objetos accesibles pueden asociarse desde GC ROOTS.

 

 2.3 Algoritmo de replicación

 Especialmente adecuado para escenas con muchos objetos basura y pocos objetos sobrevivientes; por ejemplo: áreas S0 y S1 en el área Young


2.4 Compresión de etiquetas (Cotización de marcas)

 

 

2.5 Algoritmo de recopilación incremental, algoritmo de partición

Trece Recolector de basura

13.1 Clasificación de los recolectores de basura


 

 

 

 Evaluar las métricas de rendimiento de GC

 

 

 

 

13.2 Resumen de los diferentes recolectores de basura

 

 

 

 

 

 

 

En jdk8, UseParallelGC coincide con UseParallelOldGC de forma predeterminada

Modificar la versión JDK del programa que se ejecuta en la idea.

 

 

13.3 Recolectores de Basura Antiguos en Serie y en Serie: Recolección en Serie

 

 

 

13.4 Recolector de basura ParNew: recolección en paralelo

 La capa inferior de ParNew comparte mucho código con Serial

 

 

 

13.5 Recolectores de basura antiguos paralelos y paralelos: prioridad de rendimiento

 

 

 

 

 

13.6 Colector CMS: baja latencia

 

 

 

 

 

 

 

 

 

 

 

 

 

 

13.7 Colector G1: Generación de Región

 

 

Características (Ventajas) de G1 Recycler

 

 

Desventajas del colector G1:

 

Configuración de parámetros para el colector G1

 

 

 

 

 

 

Bump-the-pointer:
una sola región utiliza la colisión de punteros para almacenar datos. Asignado arriba es el espacio de memoria utilizado, top es la posición del puntero y no asignado es el espacio de memoria no utilizado. TLAB: aunque hay regiones particionadas, hay subprocesos aún
independientes
Algo de espacio TLAB, que puede garantizar que varios subprocesos puedan modificar objetos en paralelo

 Proceso de reciclaje G1

 

 

Conjunto recordado  

 

 

 

 

 

 

 

 

 

 

 


13.8 Resumen del recolector de basura

 

 

 CMS está obsoleto en JDK9.

 

 

/**
 *  -XX:+PrintCommandLineFlags
 *
 *  -XX:+UseSerialGC:表明新生代使用Serial GC ,同时老年代使用Serial Old GC
 *
 *  -XX:+UseParNewGC:标明新生代使用ParNew GC
 *
 *  -XX:+UseParallelGC:表明新生代使用Parallel GC
 *  -XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC
 *  说明:二者可以相互激活
 *
 *  -XX:+UseConcMarkSweepGC:表明老年代使用CMS GC。同时,年轻代会触发对ParNew 的使用
 * @author shkstart  [email protected]
 * @create 2020  0:10
 */
public class GCUseTest {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();
        while(true){
            byte[] arr = new byte[100];
            list.add(arr);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


13.9 Análisis de registro de GC

 

Los dos siguientes son equivalentes
-Xms60m -Xmx60m -XX:+PrintGC
-Xms60m -Xmx60m -verbose:gc

public class GCLogTest {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 500; i++) {
            byte[] arr = new byte[1024 * 100];//100KB
            list.add(arr);
    }
}
[GC (Allocation Failure)  15289K->13782K(58880K), 0.0044617 secs]
[GC (Allocation Failure)  29081K->29184K(58880K), 0.0046940 secs]
[Full GC (Ergonomics)  29184K->28807K(58880K), 0.0102882 secs]
[Full GC (Ergonomics)  44125K->43710K(58880K), 0.0060180 secs]

GC, Full GC: El tipo de GC, GC solo se realiza en la generación joven, y Full GC incluye la generación inmortal, la nueva generación y la vieja generación.
Fallo de asignación: el motivo por el que se produjo el GC.
15289K->13782K: el tamaño del montón antes de GC y el tamaño después de GC
58880K: el tamaño total del montón.
0.0044617 segundos: La duración de GC.

-Xms60m -Xmx60m -XX:+ImprimirGCDetalles

[GC (Allocation Failure) [PSYoungGen: 15282K->2548K(17920K)] 15282K->13874K(58880K), 0.0417173 secs] [Times: user=0.00 sys=0.00, real=0.04 secs] 
[GC (Allocation Failure) [PSYoungGen: 17847K->2500K(17920K)] 29173K->29028K(58880K), 0.0073599 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 2500K->0K(17920K)] [ParOldGen: 26528K->28807K(40960K)] 29028K->28807K(58880K), [Metaspace: 3495K->3495K(1056768K)], 0.0135070 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
[Full GC (Ergonomics) [PSYoungGen: 15318K->3000K(17920K)] [ParOldGen: 28807K->40709K(40960K)] 44126K->43710K(58880K), [Metaspace: 3496K->3496K(1056768K)], 0.0167859 secs] [Times: user=0.00 sys=0.02, real=0.02 secs] 
Heap
 PSYoungGen      total 17920K, used 10253K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
  eden space 15360K, 66% used [0x00000000fec00000,0x00000000ff603510,0x00000000ffb00000)
  from space 2560K, 0% used [0x00000000ffd80000,0x00000000ffd80000,0x0000000100000000)
  to   space 2560K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffd80000)
 ParOldGen       total 40960K, used 40709K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
  object space 40960K, 99% used [0x00000000fc400000,0x00000000febc17f0,0x00000000fec00000)
 Metaspace       used 3502K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

GC, GC completo: el mismo tipo de
falla de asignación de GC: razón de GC
PsYoungGen: el cambio de tamaño antes y después del GC de nueva generación usando el recolector de basura paralelo Parallel Scavenge
ParOldGen: el tamaño antes y después del GC de generación anterior usando el paralelo Parallel Old recolector de basura Change
Metaspace: el cambio en el tamaño del área de metadatos antes y después de GC, el área de metadatos se introdujo en JDK1.8 para reemplazar la generación permanente segundos
: se refiere al tiempo empleado por GC
Times: usuario: se refiere a todos los Tiempo de CPU invertido por el sistema recolector de elementos no utilizados
: tiempo invertido en espera de llamadas al sistema o eventos del sistema
real: el tiempo desde el principio hasta el final de GC, incluido el tiempo real ocupado por otros procesos en el intervalo de tiempo.

-Xms60m -Xmx60m -XX:+ImprimirDetallesGC -XX:+ImprimirMarcasHoraGC -XX:+ImprimirMarcasFechaGC

2022-12-16T23:07:42.172+0800: 0.260: [GC (Allocation Failure) [PSYoungGen: 15282K->2544K(17920K)] 15282K->13894K(58880K), 0.0420238 secs] [Times: user=0.00 sys=0.00, real=0.04 secs] 
2022-12-16T23:07:42.213+0800: 0.270: [GC (Allocation Failure) [PSYoungGen: 17843K->2536K(17920K)] 29193K->29112K(58880K), 0.0083888 secs] [Times: user=0.01 sys=0.13, real=0.02 secs] 
2022-12-16T23:07:42.229+0800: 0.278: [Full GC (Ergonomics) [PSYoungGen: 2536K->0K(17920K)] [ParOldGen: 26576K->28807K(40960K)] 29112K->28807K(58880K), [Metaspace: 3494K->3494K(1056768K)], 0.0184285 secs] [Times: user=0.09 sys=0.00, real=0.01 secs] 
2022-12-16T23:07:42.244+0800: 0.301: [Full GC (Ergonomics) [PSYoungGen: 15318K->3000K(17920K)] [ParOldGen: 28807K->40709K(40960K)] 44125K->43710K(58880K), [Metaspace: 3495K->3495K(1056768K)], 0.0148117 secs] [Times: user=0.05 sys=0.06, real=0.02 secs] 

2022-12-16T23:07:42.172+0800: Se imprime el parámetro -XX:+PrintGCDateStamps, que indica la marca de tiempo impresa actual
0.260: Se imprime el parámetro -XX:+PrintGCTimeStamps, que indica cuánto tiempo se ha iniciado la JVM.

GC requiere mucho tiempo: real

 

 

 

/**
 * 在jdk7 和 jdk8中分别执行
 * -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
 * @author shkstart  [email protected]
 * @create 2020  0:12
 */
public class GCLogTest1 {
    private static final int _1MB = 1024 * 1024;

    public static void testAllocation() {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];
    }

    public static void main(String[] agrs) {
        testAllocation();
    }
}



JDK7

JDK8 

[GC (Allocation Failure) [DefNew: 6431K->695K(9216K), 0.0084041 secs] 6431K->4791K(19456K), 0.0085389 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 7161K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  78% used [0x00000000fec00000, 0x00000000ff2506b0, 0x00000000ff400000)
  from space 1024K,  67% used [0x00000000ff500000, 0x00000000ff5adf38, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00020, 0x00000000ffa00200, 0x0000000100000000)
 Metaspace       used 3500K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

Se puede ver que la generación anterior representa el 40%, es decir, 4M, lo que equivale a colocar objetos grandes directamente en la generación anterior después de que no quepan en el área de Eden, pero este no es el caso en JDK7. ubicado en el área de Eden

 

GCViewer es un paquete jar que se puede ejecutar haciendo clic, pero la página no se puede ajustar, la resolución no es adecuada y es difícil de usar.
GC Easy: https://gceasy.io

-Xms60m -Xmx60m -XX:+ImprimirDetallesGC -XX:+ImprimirMarcasHoraGC -XX:+ImprimirMarcasFechaGC -XX:+Imprimir HeapAtGC -Xloggc:./logs/gc.log

public class GCLogTest {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();

        for (int i = 0; i < 500; i++) {
            byte[] arr = new byte[1024 * 100];//100KB
            list.add(arr);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

./ se refiere al directorio actual, es decir, el directorio general del proyecto actual, el gc.log debajo de la carpeta de registros, cópielo en el escritorio y luego cárguelo en GCeasy para su análisis.


13.10 Nuevos Desarrollos en el Recolector de Basura

 

 

 

 

 

 

 

 

Nota: este artículo es una nota hecha al estudiar el conjunto completo de tutoriales de JVM (explicación detallada de la máquina virtual Java) por Song Hongkang de Shang Silicon Valley.

Supongo que te gusta

Origin blog.csdn.net/chuixue24/article/details/130511497
Recomendado
Clasificación