¿Qué es el internamiento de Java String?

234

¿Qué es String Interning en Java, cuándo debo usarlo y por qué ?

saplingPro
fuente
2
si String a = new String("abc"); String b = new String("abc"); entoncesa.intern() == b.intern()
Asanka Siriwardena
Ejemplo de pasantía de
Ronak Poriya
Ciervas String.intern()depende ClassLoader, es decir, hacer diferentes cargador de clases de la creación de "diferentes" Strings, causando diferentes interns?
AlikElzin-kilaka
1
@ AlikElzin-kilaka no, los cargadores de clases son completamente irrelevantes para el internamiento de cadenas. La próxima vez que tenga una pregunta, abra una nueva pregunta en lugar de publicarla como un comentario a una pregunta diferente.
Holger

Respuestas:

233

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern ()

Básicamente, hacer String.intern () en una serie de cadenas asegurará que todas las cadenas que tengan el mismo contenido compartan la misma memoria. Entonces, si tiene una lista de nombres en los que "juan" aparece 1000 veces, al internarse se asegura de que solo se le asigne una memoria a "juan".

Esto puede ser útil para reducir los requisitos de memoria de su programa. Pero tenga en cuenta que JVM mantiene la memoria caché en una agrupación de memoria permanente, que generalmente tiene un tamaño limitado en comparación con el montón, por lo que no debe usar el interno si no tiene demasiados valores duplicados.


Más sobre las restricciones de memoria de usar intern ()

Por un lado, es cierto que puede eliminar los duplicados de String internalizándolos. El problema es que las cadenas internalizadas van a la Generación Permanente, que es un área de la JVM que está reservada para objetos que no son de usuario, como Clases, Métodos y otros objetos JVM internos. El tamaño de esta área es limitado y generalmente es mucho más pequeño que el montón. Llamar a intern () en una Cadena tiene el efecto de moverlo del montón a la generación permanente, y corre el riesgo de quedarse sin espacio PermGen.

- De: http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html


Desde JDK 7 (quiero decir en HotSpot), algo ha cambiado.

En JDK 7, las cadenas internadas 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 las generaciones jóvenes y viejas), junto con los otros objetos creados por la aplicación . Este cambio dará como resultado más datos que residen en el montón principal de Java, y menos datos en la generación permanente, y por lo tanto puede requerir que se ajusten los tamaños de montón. La mayoría de las aplicaciones verán 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.

- Desde Java SE 7 Características y mejoras

Actualización: las cadenas internadas se almacenan en el montón principal desde Java 7 en adelante. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes

Ashwinee K Jha
fuente
1
"Pero tenga en cuenta que JVM mantiene la memoria caché en un grupo de memoria permanente que generalmente tiene un tamaño limitado ..." ¿Puede explicar esto? No entendí
saplingPro
2
Las cadenas "internas" se almacenan en una región de memoria especial en la JVM. Esta región de memoria suele tener un tamaño fijo y no forma parte del montón de Java normal donde se almacenan otros datos. Debido al tamaño fijo, puede suceder que esta región de memoria permanente se llene con todas sus cadenas, dando lugar a problemas feos (las clases no se pueden cargar y otras cosas).
violonchelo
@cello entonces, ¿es similar al almacenamiento en caché?
saplingPro
8
@grassPro: Sí, es un tipo de almacenamiento en caché, uno que proporciona la JVM de forma nativa. Como nota, debido a la fusión de Sun / Oracle JVM y JRockit, los ingenieros de JVM intentan deshacerse de la región de memoria permanente en JDK 8 ( openjdk.java.net/jeps/122 ), por lo que no habrá cualquier limitación de tamaño en el futuro.
violonchelo
99
Los programadores también deben ser conscientes de que el internamiento de cadenas puede tener implicaciones de seguridad. Si tiene texto confidencial, como contraseñas como cadenas en la memoria, puede permanecer en la memoria durante mucho tiempo, incluso si los objetos de cadena reales han sido GC'd durante mucho tiempo. Eso puede ser problemático si los malos tienen acceso a un volcado de memoria. Este problema existe incluso sin internación (dado que GC no es determinista para comenzar, etc.), pero lo empeora un poco. Siempre es una buena idea usarlo en char[]lugar de Stringtexto sensible y ponerlo a cero tan pronto como ya no sea necesario.
Chris
71

Hay algunas preguntas de "entrevista pegadiza", como por qué te haces igual. si ejecuta el siguiente código.

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

Si desea comparar cadenas, debe usarlas equals(). Lo anterior imprimirá igual porque el compilador testStringya lo ha internado . Puede internar las cadenas usted mismo utilizando el método interno como se muestra en las respuestas anteriores ...

maslan
fuente
55
Su ejemplo es complicado porque resultará en la misma impresión incluso si usa el equalsmétodo. Es posible que desee agregar una new String()comparación para mostrar la distinción más claramente.
giannis christofakis
@giannischristofakis pero si usamos new String (), ¿no fallaría el ==? ¿Java también internaliza automáticamente cadenas nuevas?
Deepak Selvakumar
@giannischristofakis, por supuesto, si usa una nueva cadena (), fallará en ==. pero la nueva Cadena (...). intern () no fallará en == porque el interno devolverá la misma cadena. Simple suponga que el compilador está haciendo un nuevo String (). Interno en literales
maslan
42

JLS

JLS 7 3.10.5 lo define y da un ejemplo práctico:

Además, un literal de cadena siempre se refiere a la misma instancia de la clase String. Esto se debe a que los literales de cadena, o, más generalmente, las cadenas que son valores de expresiones constantes (§15.28), están "internados" para compartir instancias únicas, utilizando el método String.intern.

Ejemplo 3.10.5-1. Literales de cuerda

El programa que consiste en la unidad de compilación (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

y la unidad de compilación:

package other;
public class Other { public static String hello = "Hello"; }

produce la salida:

true true true true false true

JVMS

JVMS 7 5.1 dice que el internado se implementa de manera mágica y eficiente con una CONSTANT_String_infoestructura dedicada (a diferencia de la mayoría de los otros objetos que tienen representaciones más genéricas):

Un literal de cadena es una referencia a una instancia de clase String, y se deriva de una estructura CONSTANT_String_info (§4.4.3) en la representación binaria de una clase o interfaz. La estructura CONSTANT_String_info proporciona la secuencia de puntos de código Unicode que constituyen el literal de cadena.

El lenguaje de programación Java requiere que los literales de cadena idénticos (es decir, los literales que contienen la misma secuencia de puntos de código) deben referirse a la misma instancia de la clase String (JLS §3.10.5). Además, si se llama al método String.intern en cualquier cadena, el resultado es una referencia a la misma instancia de clase que se devolvería si esa cadena apareciera como un literal. Por lo tanto, la siguiente expresión debe tener el valor verdadero:

("a" + "b" + "c").intern() == "abc"

Para derivar un literal de cadena, la máquina virtual Java examina la secuencia de puntos de código dada por la estructura CONSTANT_String_info.

  • Si el método String.intern se ha llamado previamente en una instancia de clase String que contiene una secuencia de puntos de código Unicode idénticos a los dados por la estructura CONSTANT_String_info, entonces el resultado de la derivación literal de cadena es una referencia a esa misma instancia de clase String.

  • De lo contrario, se crea una nueva instancia de clase String que contiene la secuencia de puntos de código Unicode dada por la estructura CONSTANT_String_info; una referencia a esa instancia de clase es el resultado de la derivación literal de cadena. Finalmente, se invoca el método interno de la nueva instancia de String.

Bytecode

Vamos a descompilar algunos bytecode de OpenJDK 7 para ver la internación en acción.

Si descompilamos:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

tenemos en el grupo constante:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

y main:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

Tenga en cuenta cómo:

  • 0y 3: ldc #2se carga la misma constante (los literales)
  • 12: se crea una nueva instancia de cadena (con un #2argumento)
  • 35: ay cse comparan como objetos normales conif_acmpne

La representación de cadenas constantes es bastante mágica en el código de bytes:

  • tiene una estructura dedicada CONSTANT_String_info , a diferencia de los objetos normales (p new String. ej. )
  • la estructura apunta a una estructura CONSTANT_Utf8_info que contiene los datos. Esos son los únicos datos necesarios para representar la cadena.

y la cita JVMS anterior parece decir que siempre que el Utf8 apuntado es el mismo, se cargan instancias idénticas ldc.

He realizado pruebas similares para campos y:

  • static final String s = "abc"apunta a la tabla constante a través del atributo ConstantValue
  • los campos no finales no tienen ese atributo, pero aún se pueden inicializar con ldc

Conclusión : existe un soporte directo de bytecode para el conjunto de cadenas y la representación de la memoria es eficiente.

Bonificación: compárelo con el grupo Integer , que no tiene soporte directo de bytecode (es decir, no CONSTANT_String_infoanalógico).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
19

Actualización para Java 8 o más . En Java 8, el espacio PermGen (generación permanente) se elimina y se reemplaza por Meta Space. La memoria del conjunto de cadenas se mueve al montón de JVM.

En comparación con Java 7, el tamaño del grupo de cadenas aumenta en el montón. Por lo tanto, tiene más espacio para cadenas internalizadas, pero tiene menos memoria para toda la aplicación.

Una cosa más, ya sabías que al comparar 2 (referencias de) objetos en Java, =="se usa para comparar la referencia de objeto" equals, se usa para comparar el contenido del objeto.

Vamos a ver este código:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

Resultado:

value1 == value2 ---> verdadero

value1 == value3 ---> falso

value1.equals(value3) ---> verdadero

value1 == value3.intern() ---> verdadero

Es por eso que debes usar ' equals' para comparar objetos de 2 cadenas. Y así es como intern()es útil.

nguyentt
fuente
2

El internamiento de cadenas es una técnica de optimización del compilador. Si tiene dos literales de cadena idénticos en una unidad de compilación, el código generado garantiza que solo se haya creado un objeto de cadena para todas las instancias de ese literal (caracteres entre comillas dobles) dentro del ensamblaje.

Soy de origen C #, así que puedo explicar dando un ejemplo de eso:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

salida de las siguientes comparaciones:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

Nota 1 : Los objetos se comparan por referencia.

Nota 2 : typeof (int) .Name se evalúa mediante el método de reflexión, por lo que no se evalúa en tiempo de compilación. Aquí estas comparaciones se hacen en tiempo de compilación.

Análisis de los resultados: 1) verdadero porque ambos contienen el mismo literal y, por lo tanto, el código generado tendrá un solo objeto que haga referencia a "Int32". Ver nota 1 .

2) verdadero porque se verifica el contenido de ambos valores, que es el mismo.

3) FALSO porque str2 y obj no tienen el mismo literal. Ver nota 2 .

Robin Gupta
fuente
3
Es más fuerte que eso. Cualquier cadena literal cargada por el mismo cargador de clases se referirá a la misma cadena. Consulte las especificaciones JLS y JVM.
Marqués de Lorne
1
@ user207421, de hecho, incluso es irrelevante a qué cargador de clases pertenece el literal de cadena.
Holger
1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.
Rohan Kshirsagar
fuente
0

Del libro Deshmukh del Programador de Java SE 11 de OCP encontré la explicación más fácil para Interning, que fue la siguiente: como las cadenas son objetos y dado que todos los objetos en Java siempre se almacenan solo en el espacio de almacenamiento dinámico, todas las cadenas se almacenan en el espacio de almacenamiento dinámico. Sin embargo, Java mantiene las cadenas creadas sin usar la nueva palabra clave en un área especial del espacio de almacenamiento dinámico, que se denomina "grupo de cadenas". Java mantiene las cadenas creadas con la nueva palabra clave en el espacio de almacenamiento dinámico normal.

El propósito del conjunto de cadenas es mantener un conjunto de cadenas únicas. Cada vez que crea una nueva cadena sin usar la nueva palabra clave, Java verifica si la misma cadena ya existe en el grupo de cadenas. Si lo hace, Java devuelve una referencia al mismo objeto String y si no lo hace, Java crea un nuevo objeto String en el conjunto de cadenas y devuelve su referencia. Entonces, por ejemplo, si usa la cadena "hola" dos veces en su código como se muestra a continuación, obtendrá una referencia a la misma cadena. De hecho, podemos probar esta teoría comparando dos variables de referencia diferentes usando el operador == como se muestra en el siguiente código:

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true

String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false 

El operador == simplemente comprueba si dos referencias apuntan al mismo objeto o no y devuelve verdadero si lo hacen. En el código anterior, str2 obtiene la referencia al mismo objeto String que se creó anteriormente. Sin embargo, str3 y str4 obtienen referencias a dos objetos String completamente diferentes. Es por eso que str1 == str2 devuelve verdadero pero str1 == str3 y str3 == str4 devuelven falso. De hecho, cuando haces una nueva cadena ("hola"); se crean dos objetos String en lugar de solo uno si es la primera vez que se usa la cadena "hello" en cualquier parte del programa: uno en el grupo de cadenas debido al uso de una cadena entre comillas y uno en el espacio de almacenamiento dinámico normal porque del uso de nueva palabra clave.

La agrupación de cadenas es la forma en que Java guarda la memoria del programa al evitar la creación de múltiples objetos de cadena que contienen el mismo valor. Es posible obtener una cadena del conjunto de cadenas para una cadena creada con la nueva palabra clave mediante el método interno de String. Se llama "internamiento" de objetos de cadena. Por ejemplo,

String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj

System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
Hamza
fuente