Java Constant Pool es un tema duradero y también es el favorito del entrevistador. Hay muchos tipos de preguntas. Lo resumiré esta vez.
teoría
Primero, expresemos la distribución de memoria virtual JVM:
El mostrador del programa es una tubería para que JVM ejecute el programa, almacenando algunas instrucciones de salto. Esto es demasiado profundo y no entiendo.
La pila de métodos locales es la pila utilizada por JVM para llamar a los métodos del sistema operativo.
La pila de máquinas virtuales es la pila utilizada por JVM para ejecutar el código Java.
El área de método almacena algunas constantes, variables estáticas, información de clase, etc., que puede entenderse como la ubicación de almacenamiento del archivo de clase en la memoria.
El montón de máquina virtual es el montón utilizado por JVM para ejecutar el código Java.
Las piscinas constantes en Java en realidad se dividen en dos formas: piscinas constantes estáticas y grupos constantes de tiempo de ejecución .
El llamado grupo constante estático es el grupo constante en el archivo *.class. El grupo constante en el archivo de clase no solo contiene literales de cadena (número), sino que también contiene información sobre clases y métodos, que ocupa la mayor parte del espacio del archivo de clase.
El grupo constante de tiempo de ejecución es que la máquina virtual JVM carga el grupo constante en el archivo de clase en la memoria después de completar la operación de carga de clase y la guarda en el área de método . La piscina constante que a menudo llamamos se refiere a la piscina constante de tiempo de ejecución en el área del método.
A continuación, citamos algunos ejemplos de grupos constantes que son populares en Internet y luego los explican.
Cadena s1 = "hola"; Cadena s2 = "hola"; Cadena s3 = "hel" + "lo"; Cadena s4 = "hel" + nueva cadena ("lo"); Cadena S5 = nueva cadena ("Hello"); Cadena s6 = s5.intern (); Cadena S7 = "H"; Cadena s8 = "ello"; Cadena S9 = S7 + S8; System.out.println (S1 == S2); // truesystem.out.println (S1 == S3); // truesystem.out.println (S1 == S4); // falsesystem.out.println (S1 == S9); // falsesystem.out.println (S4 == S5); // falsesystem.out.println (S1 == S6); // verdaderoEn primer lugar, en Java, el operador == se usa directamente, y las direcciones de referencia de dos cadenas se comparan, no el contenido. Utilice string.equals () para comparar el contenido.
S1 == S2 es muy fácil de entender. Cuando se asignan S1 y S2, usan literales de cadena. Para decirlo sin rodeos, escriben directamente la cadena hasta la muerte. Durante la compilación, este literal se colocará directamente en el grupo constante del archivo de clase, dando así la reutilización. Después de cargar el grupo constante en tiempo de ejecución, S1 y S2 apuntan a la misma dirección de memoria, por lo que son iguales.
Hay un pozo en S1 == S3. Aunque S3 es una cadena empalmada dinámicamente, todas las partes involucradas en el empalme son literales conocidos. Durante el período de compilación, este empalme se optimizará, y el compilador lo ayudará directamente a empalmarlo. Por lo tanto, String S3 = "Hel" + "Lo"; está optimizado para string s3 = "hello"; En el archivo de clase, por lo que S1 == S3 es verdadero.
S1 == S4, por supuesto, no es igual. Aunque S4 también está empalmado, la nueva parte ("LO") no es una parte literal conocida, sino una parte impredecible. El compilador no lo optimizará. Debe esperar hasta que se ejecute para determinar el resultado. Combinado con el teorema de invariancia de la cadena , sabe dónde se asigna S4, por lo que la dirección debe ser diferente. Una breve imagen para aclarar la idea:
S1 == S9 no es igual, y la razón es similar. Aunque los literales de cadena utilizados por S7 y S8 al asignar valores, al empalmarse en S9, S7 y S8 son impredecibles. Después de todo, el compilador es un compilador y no puede usarse como intérprete, por lo que no está optimizado. Cuando se ejecuta, la nueva cadena empalmada en S7 y S8 no está segura en el montón y no puede ser la misma que la dirección S1 en el grupo constante del área del método.
S4 == S5 ya no es necesario explicarse, definitivamente no es igual, ambos están en el montón, pero las direcciones son diferentes.
La igualdad de S1 == S6 se atribuye completamente al método interno. S5 está en el montón y el contenido es hola. El método interno intentará agregar la cadena Hello al grupo constante y devolver su dirección en el grupo constante. Debido a que hay una cadena de saludo en el grupo constante, el método interno devuelve directamente la dirección; Mientras que S1 ya apunta al grupo constante durante el período de compilación, por lo que S1 y S6 apuntan a la misma dirección, que es igual.
En este punto, podemos sacar tres conclusiones muy importantes:
Debe prestar atención al comportamiento durante el período de compilación para comprender mejor el grupo constante.
Las constantes en el grupo de ejecución constante provienen básicamente del grupo constante en cada archivo de clase.
Cuando el programa se está ejecutando, JVM no agregará automáticamente constantes al grupo constante a menos que agrega constantes manualmente al grupo constante (como llamar al método interno).
Lo anterior solo implica piscinas constantes de cuerda. De hecho, hay piscinas constantes enteras, piscinas constantes de punto flotante, etc., pero son similares, pero no se pueden agregar grupos constantes de tipos numéricos. Las constantes en el grupo constante se determinan cuando comienza el programa. Por ejemplo, el rango constante en el grupo constante entero es: -128 ~ 127. Solo se pueden usar números en este rango para la piscina constante.
práctica
Dicho tanta teoría, toquemos el grupo constante real.
Como se mencionó anteriormente, hay un grupo constante estático en el archivo de clase. El compilador genera este grupo constante y se utiliza para almacenar literales en el archivo fuente de Java (este artículo solo se centra en literales). Supongamos que tenemos el siguiente código Java:
Cadena S = "HI";
Por conveniencia, es así de simple, ¡eso es correcto! Después de compilar el código en un archivo de clase, use WinHex para abrir el archivo de clase de formato binario. Como se muestra en la imagen:
Expliquemos brevemente la estructura del archivo de clase. Los 4 bytes al principio son el número mágico del archivo de clase, que se utiliza para identificar esto como un archivo de clase. Para decirlo sin rodeos, es el encabezado del archivo, que es: ca fe ba ser.
Los siguientes 4 bytes son el número de versión de Java, y el número de versión aquí es 34, porque el autor se compila con JDK8, y el número de versión corresponde al nivel de la versión JDK. La versión más alta puede ser compatible con la versión inferior, pero la versión inferior no puede ejecutar la versión más alta. Entonces, si un día los lectores quieren saber con qué se compila el archivo de clase de otras personas JDK, puede ver estos 4 bytes.
El siguiente es la entrada constante de la piscina. El número de constantes constantes de piscinas se identifica con 2 bytes en la entrada. En este ejemplo, el valor es 00 1A. Se traduce al decimal y tiene 26, lo que significa que hay 25 constantes. La constante 0 es un valor especial, por lo que solo hay 25 constantes.
La piscina constante almacena varios tipos de constantes. Todos tienen sus propios tipos y sus propias especificaciones de almacenamiento. Este artículo solo se centra en las constantes de cadena. Las constantes de cadena comienzan con 01 (1 byte), y luego registre la longitud de la cadena con 2 bytes, y luego el contenido real de la cadena. En este caso, es: 01 00 02 68 69.
A continuación, hablemos de la piscina constante de tiempo de ejecución. Dado que la piscina constante de tiempo de ejecución está en el área del método, podemos establecer el tamaño del área del método a través de los parámetros JVM: -xx: Permsize, -xx: MaxPermizize, lo que limita indirectamente el tamaño constante de la piscina.
Suponga que el parámetro de inicio JVM es: -xx: permSize = 2m -xx: maxPermsize = 2m, y luego ejecuta el siguiente código:
// Mantenga referencias para evitar la lista automática de recolección de basura <String> list = new ArrayList <String> (); int i = 0; while (true) {// Agregue manualmente list.add (string.valueOf (i ++). Intern ());}El programa lanzará inmediatamente: Excepción en el hilo "principal" java.lang.ouTOfMemoryError: Excepción de espacio de Permgen. El espacio de Permgen es el área del método, que es suficiente para indicar que la piscina constante está en el área del método.
En JDK8, se retiró el área del método y se reemplazó el área de MetaSpace. Por lo tanto, necesitamos usar el nuevo parámetro JVM: -xx: maxmetApacesize = 2m, y aún así ejecutar el código anterior, lanzando: java.lang.outOfMemoryError: excepción metaSpace. Del mismo modo, se explica que la piscina constante de tiempo de ejecución se divide en el área de Metaspace. Para obtener un conocimiento específico sobre el área de Metaspace, busquelo usted mismo.
Todos los códigos en este artículo han sido probados y aprobados bajo JDK7 y JDK8. Otras versiones de JDK pueden tener ligeras diferencias. Por favor explúrelo usted mismo.