¿Dónde vive el grupo constante String de Java, el montón o la pila?

104

Conozco el concepto de un grupo de constantes y el grupo de constantes de cadena utilizado por las JVM para manejar literales de cadena. Pero no sé qué tipo de memoria utiliza la JVM para almacenar literales constantes de cadena. ¿La pila o el montón? Dado que es un literal que no está asociado con ninguna instancia, asumiría que se almacenará en la pila. Pero si no es referido por ninguna instancia, el literal debe ser recopilado por GC run (corríjame si me equivoco), entonces, ¿cómo se maneja eso si está almacenado en la pila?

Rengasami Ramanujam
fuente
11
¿Cómo se puede almacenar una piscina en la pila? ¿conoces el concepto de pila?
The Scrum Meister
1
Hola Scrum Meister, intenté decir que no puede ser. Perdón por la convención equivocada. Respecto a GC Hace un momento me he enterado. Gracias por eso
Rengasami Ramanujam
@TheScrumMeister: de hecho, en algunas circunstancias, pueden ser recolectados como basura. El "factor decisivo" es que el objeto de código de cualquier clase que mencione un literal de cadena tendrá una referencia al objeto de cadena que representa el literal.
Stephen C

Respuestas:

74

La respuesta es técnicamente ninguna de las dos. Según la especificación de la máquina virtual de Java, el área para almacenar literales de cadena está en el grupo de constantes de tiempo de ejecución . El área de memoria del grupo constante de tiempo de ejecución se asigna por clase o por interfaz, por lo que no está vinculada a ninguna instancia de objeto. El grupo de constantes de tiempo de ejecución es un subconjunto del área de métodos que "almacena estructuras por clase, como el grupo de constantes de tiempo de ejecución, datos de campo y método, y el código para métodos y constructores, incluidos los métodos especiales utilizados en la inicialización e interfaz de clases e instancias. tipo de inicialización ". La especificación de VM dice que aunque el área de método es lógicamente parte del montón, no dicta que la memoria asignada en el área de método esté sujeta a la recolección de basura u otros comportamientos que se asociarían con las estructuras de datos normales asignadas al montón.

Duane Moore
fuente
8
En realidad, cuando las clases se cargan en la VM, las constantes de cadena se copiarán en el montón, en un grupo de cadenas de toda la VM (en el permgen, como dijo Stephen C), ya que los literales de cadena iguales en diferentes clases tienen que ser los mismo objeto String (por JLS).
Paŭlo Ebermann
1
Gracias a todos por sus respuestas o comentarios. Entendí mucho con esta discusión. Es bueno conocerlos, chicos :)
Rengasami Ramanujam
4
Paŭlo, eso es cierto para la máquina virtual de Sun, pero no necesariamente es cierto para todas las implementaciones de la JVM. Como menciona la especificación JVM, aunque el grupo de constantes de tiempo de ejecución y el área de métodos son lógicamente parte del montón, no tienen que tener el mismo comportamiento. Solo una pequeña diferencia semántica, de verdad :)
Duane Moore
54

Como se explica en esta respuesta , la ubicación exacta del grupo de cadenas no se especifica y puede variar de una implementación de JVM a otra.

Es interesante notar que hasta Java 7, el grupo estaba en el espacio permgen del montón en JVM de hotspot pero se ha movido a la parte principal del montón desde Java 7 :

Área : HotSpot
Sinopsis : En JDK 7, las cadenas internas ya no se asignan en la generación permanente del montón de Java, sino que se asignan en la parte principal del montón de Java (conocidas como generaciones jóvenes y viejas), junto con el resto objetos creados por la aplicación. Este cambio dará como resultado que más datos residan en el montón principal de Java y menos datos en la generación permanente y, por lo tanto, es posible que sea necesario ajustar el tamaño del montón. La mayoría de las aplicaciones verán solo diferencias relativamente pequeñas en el uso del montón debido a este cambio, pero las aplicaciones más grandes que cargan muchas clases o hacen un uso intensivo del método String.intern () verán diferencias más significativas. RFE: 6962931

Y en Java 8 Hotspot, la generación permanente se ha eliminado por completo.

Assylias
fuente
30

Los literales de cadena no se almacenan en la pila. Nunca. De hecho, no se almacenan objetos en la pila.

Los literales de cadena (o más exactamente, los objetos de cadena que los representan) se almacenaron históricamente en un montón llamado montón "permgen". (Permgen es la abreviatura de generación permanente).

En circunstancias normales, los literales String y gran parte del resto del contenido del montón de permgen son accesibles "permanentemente" y no se recolectan como basura. (Por ejemplo, los literales de cadena siempre son accesibles desde los objetos de código que los usan). Sin embargo, puede configurar una JVM para intentar buscar y recolectar clases cargadas dinámicamente que ya no son necesarias, y esto puede hacer que los literales de cadena se recolecten como basura .

ACLARACIÓN # 1 - No estoy diciendo que Permgen no reciba GC'ed. Lo hace, normalmente cuando la JVM decide ejecutar un GC completo. Mi punto es que los literales de cadena serán accesibles siempre que el código que los usa sea accesible, y el código será accesible siempre que se pueda acceder al cargador de clases del código, y para los cargadores de clases predeterminados, eso significa "para siempre".

ACLARACIÓN # 2 - De hecho, Java 7 y versiones posteriores usan el montón regular para contener el grupo de cadenas. Por lo tanto, los objetos String que representan los literales String y las cadenas internas están en realidad en el montón normal. (Vea la respuesta de @ assylias para más detalles).


Pero todavía estoy tratando de encontrar una línea delgada entre el almacenamiento de literal de cadena y la cadena creada con new.

No hay una "línea delgada". Es realmente muy simple:

  • String los objetos que representan / corresponden a cadenas literales se mantienen en el grupo de cadenas.
  • Stringlos objetos que fueron creados por una String::internllamada se mantienen en el grupo de cadenas.
  • Todos los demás Stringobjetos NO se mantienen en el grupo de cuerdas.

Luego está la pregunta separada de dónde se "almacena" el grupo de cadenas. Antes de Java 7, era el montón de permgen. Desde Java 7 en adelante, es el montón principal.

Stephen C
fuente
23

Agrupación de cadenas

La agrupación de cadenas (a veces también llamada canonicalización de cadenas) es un proceso de sustitución de varios objetos String con el mismo valor pero con una identidad diferente con un solo objeto String compartido. Puede lograr este objetivo manteniendo su propio mapa (con referencias posiblemente suaves o débiles según sus requisitos) y utilizando valores de mapa como valores canonicalizados. O puede usar el método String.intern () que le proporciona JDK.

En momentos de Java 6, el uso de String.intern () estaba prohibido por muchos estándares debido a la alta posibilidad de obtener una OutOfMemoryException si la agrupación se salía de control. La implementación de Oracle Java 7 de la agrupación de cadenas se modificó considerablemente. Puede buscar detalles en http://bugs.sun.com/view_bug.do?bug_id=6962931 y http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () en Java 6

En aquellos viejos tiempos, todas las cadenas internas se almacenaban en PermGen, la parte de tamaño fijo del montón que se usaba principalmente para almacenar clases cargadas y grupos de cadenas. Además de las cadenas internadas explícitamente, el grupo de cadenas PermGen también contenía todas las cadenas literales utilizadas anteriormente en su programa (aquí se usa la palabra importante: si una clase o método nunca se cargó / llamó, no se cargarán las constantes definidas en él).

El mayor problema con ese grupo de cadenas en Java 6 fue su ubicación: PermGen. PermGen tiene un tamaño fijo y no se puede expandir en tiempo de ejecución. Puede configurarlo usando la opción -XX: MaxPermSize = 96m. Hasta donde yo sé, el tamaño predeterminado de PermGen varía entre 32M y 96M dependiendo de la plataforma. Puede aumentar su tamaño, pero su tamaño seguirá siendo fijo. Dicha limitación requería un uso muy cuidadoso de String.intern; es mejor que no interrumpa ninguna entrada de usuario no controlada utilizando este método. Es por eso que la agrupación de cadenas en tiempos de Java 6 se implementó principalmente en los mapas administrados manualmente.

String.intern () en Java 7

Los ingenieros de Oracle realizaron un cambio extremadamente importante en la lógica de agrupación de cadenas en Java 7: la agrupación de cadenas se reubicó en el montón. Significa que ya no está limitado por un área de memoria de tamaño fijo separada. Todas las cadenas están ahora ubicadas en el montón, como la mayoría de los otros objetos ordinarios, lo que le permite administrar solo el tamaño del montón mientras ajusta su aplicación. Técnicamente, esto por sí solo podría ser una razón suficiente para reconsiderar el uso de String.intern () en sus programas Java 7. Pero existen otras razones.

Los valores del grupo de cadenas se recolectan como basura

Sí, todas las cadenas de la agrupación de cadenas de JVM son elegibles para la recolección de basura si no hay referencias a ellas desde las raíces de su programa. Se aplica a todas las versiones analizadas de Java. Significa que si su cadena interna quedó fuera del alcance y no hay otras referencias a ella, será recolectada basura del grupo de cadenas JVM.

Al ser elegible para la recolección de basura y residir en el montón, un grupo de cadenas de JVM parece ser un lugar adecuado para todas sus cadenas, ¿no es así? En teoría, es cierto: las cadenas no utilizadas se recolectarán como basura del grupo, las cadenas usadas le permitirán ahorrar memoria en caso de que obtenga una cadena igual de la entrada. ¿Parece ser una estrategia perfecta para ahorrar memoria? Casi así. Debe saber cómo se implementa el grupo de cadenas antes de tomar cualquier decisión.

fuente.

Molesto
fuente
11

Como explican otras respuestas, la memoria en Java se divide en dos partes

1. Pila: se crea una pila por subproceso y almacena marcos de pila que nuevamente almacenan variables locales y si una variable es un tipo de referencia, entonces esa variable se refiere a una ubicación de memoria en el montón para el objeto real.

2. Montón: todo tipo de objetos se crearán únicamente en el montón.

La memoria del montón se vuelve a dividir en 3 porciones

1. Young Generation: almacena objetos que tienen una vida corta, Young Generation en sí se puede dividir en dos categorías Eden Space y Survivor Space .

2. Old Generation: Almacene objetos que han sobrevivido a muchos ciclos de recolección de basura y todavía se les hace referencia.

3. Generación permanente: almacena metadatos sobre el programa, por ejemplo, grupo de constantes en tiempo de ejecución.

El grupo de constantes de cadena pertenece al área de generación permanente de la memoria Heap.

Podemos ver el grupo de constantes en tiempo de ejecución para nuestro código en el código de bytes usando el javap -verbose class_namecual nos mostrará referencias de métodos (#Methodref), objetos de clase (#Class), literales de cadena (#String)

grupo de constantes de tiempo de ejecución

Puede leer más sobre esto en mi artículo ¿Cómo maneja la JVM la sobrecarga y la anulación de métodos internamente ?

Naresh Joshi
fuente
Por favor, revele cualquier afiliación y no utilice el sitio como una forma de promocionar su sitio mediante publicaciones. Consulte ¿Cómo escribo una buena respuesta? .
9

A las excelentes respuestas que ya se incluyen aquí, quiero agregar algo que falta en mi perspectiva: una ilustración.

Como ya, JVM divide la memoria asignada a un programa Java en dos partes. uno es pila y otro es montón . La pila se usa para fines de ejecución y el montón se usa para fines de almacenamiento. En esa memoria de pila, JVM asigna algo de memoria especialmente diseñada para cadenas literales. Esta parte de la memoria dinámica se denomina grupo de constantes de cadena .

Entonces, por ejemplo, si inicia los siguientes objetos:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

String literales s1e s2irán al grupo de constantes de cadena, objetos obj1, obj2, obj3 al montón. Todos ellos, serán referenciados desde la Pila.

Además, tenga en cuenta que "abc" aparecerá en el montón y en el grupo constante de cadenas. ¿Por qué se crea String s1 = "abc"y String obj1 = new String("abc")se creará de esta manera? Es porque String obj1 = new String("abc")crea explícitamente una instancia nueva y referencialmente distinta de un objeto String y String s1 = "abc"puede reutilizar una instancia del grupo de constantes de cadena si hay una disponible. Para una explicación más elaborada: https://stackoverflow.com/a/3298542/2811258

ingrese la descripción de la imagen aquí

Johnny
fuente
En el diagrama dado, ¿dónde existirían los literales "def" y "456"? ¿Y cómo se haría referencia a estos?
Satyendra
Gracias por tu comentario @Satyendra, he actualizado la ilustración y la respuesta.
Johnny
@Stas por qué se crea otro objeto String "abc" ... debería usar la referencia obj1 para señalar el literal, ¿verdad?
Es porque String obj1 = new String ("abc") crea explícitamente una instancia nueva y referencialmente distinta de un objeto String y String s1 = "abc" puede reutilizar una instancia del grupo de constantes de cadena si hay una disponible. Para una explicación más elaborada: stackoverflow.com/a/3298542/2811258
Johnny