JVM (7): comprensión profunda de String utilizando el modelo de memoria Java

Referencia: "Conocimiento profundo de la máquina virtual Java"

"Tutorial de Song Hongkang JVM"

Cuando estaba aprendiendo los conceptos básicos de Java, escribí un pensamiento sobre la clase String ( algunos pensamientos sobre String ), pero en ese momento no entendía el modelo de memoria de Java. En ese momento, para demostrar si era correcto, utilizó la descompilación y comprobó los bytes. Instrucciones de código. Pero String está lejos de ser tan simple.Después de comprender el modelo de memoria de Java, especialmente después de escuchar el tutorial de String del profesor Song Hongkang, tengo una comprensión más profunda de String.

1. Introducción

Primero, dé algunas preguntas comunes de la entrevista. Si tiene respuestas muy positivas a las siguientes preguntas de la entrevista, entonces este artículo no es para usted.

  1. Las siguientes operaciones han creado un total de varios objetos
String str1 = new String("1");	//2个(不包括引用类型)
String str2 = new String("1") + new String("2");	//6个(不包括引用类型)
  1. ¿verdadero o falso?
String str1 = new String("1");
String srt2 = str1.intern();
String str3 = "1";
System.out.println(str1 == srt2);	//jdk6: false jdk7/jdk8:false
System.out.println(srt2 == str3);	//jdk6:	true  jdk7/jdk8:true
  1. ¿verdadero o falso?
String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";
System.out.println(s1 == s2);	//jdk6:false	jdk7/jdk8:true

2. Revisión de las características básicas de String

  • Generalmente hay dos formas de crear una cadena:
String s1 = "hello";	//字面量的定义方式,这里声明的字符串在字符串常量池中
String s2 = new String("hello");
  • La cadena no es heredable, se declara final

  • String implementa la interfaz serializable: significa que la cadena admite la serialización. Implementó la interfaz Comparable: esa cadena se puede comparar en tamaño

  • String define el carácter final [] internamente en jdk8 y antes, y el valor se usa para almacenar datos de cadena. Cuando jdk9 se cambia al byte final []

  • Comprender la inmutabilidad de String:

    • Al reasignar una cadena, debe volver a escribir la asignación en el área de memoria designada y no puede usar el valor original para la asignación.
    • Al concatenar cadenas existentes, también debe volver a especificar la asignación del área de memoria y no puede usar el valor original para la asignación.
    • Al llamar al método replace () de String para modificar el carácter o la cadena especificada, también necesita volver a especificar el área de memoria para la asignación, y no puede usar el valor original para la asignación.

Tres, algo de comprensión del grupo constante de cuerdas

1. ¿Por qué hay un grupo constante de cadenas?
  • La asignación de cadenas, al igual que otras asignaciones de objetos, consume un alto costo de tiempo y espacio.Cree una gran cantidad de cadenas con frecuencia, lo que afecta en gran medida el rendimiento del programa.
  • JVMPara mejorar el rendimiento y reducir la sobrecarga de memoria, Se realizan algunas optimizaciones al crear instancias de constantes de cadena, por lo que se abre un grupo de constantes de cadena para cadenas, similar a un área de búfer
  • Al crear una constante de cadena, primero verifique si la cadena existe en el grupo de constantes de cadena
  • Las cadenas con el mismo contenido no se almacenarán en el grupo de constantes de cadenas
  • El grupo constante es similar a un caché proporcionado por el nivel del sistema JAVA
2. Detalles de la evolución
  • En jdk6, el grupo de constantes de cadena se coloca en la generación permanente (el área de método se implementa en hostpot)
  • En jdk7 (el área del método jdk8 se ha cambiado a metaespacio, implementado en la memoria local, jdk7 es equivalente al tiempo de transición entre la generación permanente y el metaespacio) y arriba, el grupo de constantes de cadena se coloca en el montón

¿Por qué ajustar la ubicación del grupo de constantes de cadena?

Si los desarrolladores de jdk desean realizar los cambios anteriores, deben ocuparse del rendimiento general. En jdk6, el grupo de constantes de tiempo de ejecución está en la generación permanente, y la frecuencia de recolección de basura de la generación permanente es muy baja (la recolección de basura de la generación permanente activa Full GC), e incluso la especificación de la máquina virtual Jvm no requiere el método área a realizar Recolección de basura, pero las cadenas se usan con frecuencia y algunas constantes no utilizadas deben recolectarse. Si el grupo de constantes de cadena se coloca en el montón, el montón es el área clave de la recolección de basura, por lo que el grupo de constantes de cadena se puede reciclar a tiempo

3. Verifique la existencia del grupo de constantes de cadena
System.out.println();//3279
System.out.println("1");
System.out.println("2");
System.out.println("3");
System.out.println("4");
System.out.println("5");
System.out.println("6");
System.out.println("7");
System.out.println("8");
System.out.println("9");
System.out.println("10");//3289

System.out.println("1");//3289
System.out.println("2");//3289
System.out.println("3");//3289
System.out.println("4");//3289
System.out.println("5");//3289
System.out.println("6");//3289
System.out.println("7");//3289
System.out.println("8");//3289
System.out.println("9");//3289
System.out.println("10");//3289

Salida de 10 cadenas, un programa muy simple. La salida de los primeros diez números se creará porque no hay un grupo de constantes de cadena, pero los últimos 10 se tomarán directamente del grupo de constantes de cadena porque hay un grupo de constantes de cadena. Valor, por lo que la cantidad de cadenas no aumentará. Puede depurar el programa y luego abrir la ventana de memoria para ver la cantidad de cadenas. Consulte la figura siguiente para obtener más detalles:

Si no hay un grupo de constantes de cadenas, los siguientes 10 también crearán cadenas nuevamente. El número de cadenas en la memoria debería aumentar, pero el número no ha aumentado. Se puede ver que hay una estructura de grupo de constantes de cadenas en Java para mejorar eficiencia Ahorre memoria.

Operación de empalme de cuatro cuerdas

Debido a la inmutabilidad de String, ¿la cadena después de la operación de concatenación de cadenas está en el grupo constante de cadenas o es un objeto almacenado en el montón? Primero da una conclusión

  • El resultado del empalme de constantes y constantes está en el grupo constante, y el principio es la optimización durante la compilación.
  • Siempre que uno de ellos sea una variable, el resultado está en el montón. El principio de empalme de variables es StringBuilder
  • Si el resultado del empalme llama al método intern (), el objeto de cadena que no está en el grupo constante se coloca activamente en el grupo y se devuelve la dirección del objeto.

Ejemplo 1:

@Test
public void test1(){
    
    
    String s1 = "a" + "b" + "c";    //编译期优化:等同于"abc"
    String s2 = "abc";              //"abc"一定是放在字符串常量池中,将此地址赋给s2
    System.out.println(s1 == s2);    //true
    System.out.println(s1.equals(s2));  //true

}

Ejemplo 2:

@Test
public void test2(){
    
    
    String s = "a";
    String s1 = "b";
    String s3 = "ab";
    String s4 = s + s1;
    System.out.println(s3 == s4);//false

}

Código de bytes de bajo nivel:

 0 ldc #6 <a>
 2 astore_1
 3 ldc #7 <b>
 5 astore_2
 6 ldc #8 <ab>
 8 astore_3
 9 new #9 <java/lang/StringBuilder>
12 dup
13 invokespecial #10 <java/lang/StringBuilder.<init>>
16 aload_1
17 invokevirtual #11 <java/lang/StringBuilder.append>
20 aload_2
21 invokevirtual #11 <java/lang/StringBuilder.append>
24 invokevirtual #12 <java/lang/StringBuilder.toString>
27 astore 4
29 getstatic #3 <java/lang/System.out>
32 aload_3
33 aload 4
35 if_acmpne 42 (+7)
38 iconst_1
39 goto 43 (+4)
42 iconst_0
43 invokevirtual #4 <java/io/PrintStream.println>
46 return

Como se puede ver en el archivo de código de bytes, la capa inferior de la operación de empalme "+" (si hay una variable) usa StringBuilder, lo que significa que la operación de la capa inferior en el ejemplo anterior debería ser:

StringBuilder s = new StringBuilder();
s.append("a")
s.append("b")
s.toString()  --> 约等于 new String("ab")

5. Comparación de la eficiencia de append y "+"

@Test
public void test3(){
    
    
    double startTime  = System.currentTimeMillis();
    //method1(100000);  //3450
    method2(100000);    //15
    double endTime = System.currentTimeMillis();
    System.out.println(endTime - startTime);
}
public void method1(int highLevel){
    
    
    String src = "";
    for (int i = 0; i < highLevel; i++) {
    
    
        src = src + "a";
    }
}
public void method2(int highLevel){
    
    
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < highLevel; i++) {
    
    
        sb.append("a");
    }
    String src = sb.toString();
}

Usando el método de empalme de cuerdas, tomó 3450 ms, mientras que el método de adición solo usó 15 ms para ser visto. Usar agregar para empalmar cuerdas mejora en gran medida la eficiencia, lo que se puede considerar desde los dos aspectos siguientes:

  • El método append () de StringBuilder: solo se ha creado un objeto StringBuilder de principio a fin, mientras que el método de empalme de cadenas utilizando String: se han creado varios objetos StringBuilder y String

  • Método de empalme de cadenas utilizando String: debido a que se crean más objetos StringBuilder y String en la memoria, la memoria ocupa más; si se realiza GC, se necesita más tiempo.

Seis, método interno

1. Crea un total de varios objetos

String tiene un método interno, que consiste en que el objeto de carácter actual (objetos fuera de los nuevos) se puede obtener del grupo de constantes utilizando el método interno. Si el string no existe en el grupo de constantes, cree una nueva cadena de este tipo y colóquelo en la piscina constante.

En términos sencillos, el método Intern es garantizar que solo haya una copia de la cadena en la memoria, lo que puede ahorrar espacio en la memoria y acelerar la ejecución de las tareas de manipulación de cadenas. Tenga en cuenta que este valor se almacenará en String Intern Pool.

Entonces, volvamos a la pregunta inicial, cuántos objetos se crean con el siguiente código:

String str1 = new String("1");	//2个(不包括引用类型)
String str2 = new String("1") + new String("2");	//6个(不包括引用类型)

Para una nueva cadena ("1"):

Ver instrucciones de código de bytes (nota: la referencia str1 no se incluye aquí):

 0 new #2 <java/lang/String>
 3 dup
 4 ldc #3 <1>
 6 invokespecial #4 <java/lang/String.<init>>
 9 pop
10 return

Entonces aquí hay 2 objetos creados:

  • La nueva palabra clave se crea en el espacio del montón.

  • Objeto "1" en el grupo de constantes de cadena

Hay una pregunta aquí. No entendía cuando asistía a la clase. ¿Por qué los objetos en la cadena de caracteres son constantes? De hecho, cuando aprendí Java por primera vez, hice hincapié en que todo es un objeto. Por el contrario, si aquí no es el objeto en sí, sino una referencia, ¿cómo hace que diferentes objetos se refieran al mismo valor en el grupo de constantes de cadena, es decir, cómo puede ser correcto el siguiente código?

String s1 = "1";
String s2 = "1";
System.out.println(s1 == s2);		//true

La respuesta más correcta es encontrar el documento oficial, el documento oficial ya ha dicho, este objeto String, así que aquí también hay un objeto

por nueva Cadena ("1") + nueva Cadena ("2");

Ver instrucciones de código de bytes:

 0 new #2 <java/lang/StringBuilder>
 3 dup
 4 invokespecial #3 <java/lang/StringBuilder.<init>>
 7 new #4 <java/lang/String>
10 dup
11 ldc #5 <1>
13 invokespecial #6 <java/lang/String.<init>>
16 invokevirtual #7 <java/lang/StringBuilder.append>
19 new #4 <java/lang/String>
22 dup
23 ldc #8 <2>
25 invokespecial #6 <java/lang/String.<init>>
28 invokevirtual #7 <java/lang/StringBuilder.append>
31 invokevirtual #9 <java/lang/StringBuilder.toString>
34 astore_1
35 return
  • Objeto 1: nuevo StringBuilder ()
  • Objeto 2: nueva cadena ("1")
  • Objeto 3: "1" en el grupo constante
  • Objeto 4: nueva cadena ("2")
  • Objeto 5: "2" en el grupo constante
  • Objeto 6: nueva cadena ("12")

Hay un punto muy crucial aquí., Si el objeto 6 genera un objeto en el grupo de constantes de cadena, si se usa directamente new String ("12"), entonces debe haber una copia en la cadena, y se generará un objeto (siempre que no haya "12") "en el carácter antes del grupo de constantes String). Pero aquí está el método toString en StringbufferNo generará objetos "12" en el grupo de constantes de cadena

Entonces, en resumen, se generan un total de 6 objetos.

2. Explicaciones detalladas de varias preguntas de la entrevista

Consulte el artículo del equipo técnico de Meituan . De hecho, el artículo ya lo dejó muy claro. Lo volví a contar aquí solo para profundizar el impacto, ¡y esforzarme por pasar de lo superfluo a terminar!

public void test5(){
    
    
    String s = new String("1");	①
    s.intern();					②		
    String s2 = "1";		·	③
    System.out.println(s == s2);④

    String s3 = new String("1") + new String("1");	⑤
    s3.intern();									⑥
    String s4 = "11";								⑦
    System.out.println(s3 == s4);}

Con respecto al tema del método interno, debemos estar al tanto de los cambios entre las diferentes versiones de jdk. Antes de jdk6, el grupo de constantes de cadena estaba en la generación permanente, y desde jdk7, el grupo de constantes de cadena se ha cambiado al espacio de almacenamiento dinámico, entonces responda esto Este tema debe discutirse en la versión opuesta.

El resultado de lo anterior es:

  • jdk6 abajofalse false
  • jdk7 abajofalse true

En el código anterior, la línea ① crea una cadena usando el nuevo método String. Este método creará dos cadenas, s apunta a la instancia en el montón, y la línea ② usa el método interno para encontrar si hay un grupo constante de cadenas "1 ", busque allí, por lo que esta línea no tiene ningún efecto práctico, línea ③, porque ya hay" 1 "en el grupo de constantes de cadena, luego devuelva directamente el objeto a s2, por lo que si es jdk6 o jdk7 (o superior), S apunta al montón, s2 apunta al conjunto de constantes de cadena, por lo que la impresión debe ser falsa

Aquí está el segundo fragmento de código:

En el segundo fragmento de código, se crean un total de 4 objetos en la línea ⑤: un nuevo StringBuilder, dos nuevos String ("1") y uno ("11"). ToString. Aquí, el primer fragmento de código ya está en el grupo de constantes de cadena. Creado "1".Entonces, el tema central que debe preocuparnos aquí es si hay un objeto "11" en el grupo de constantes de cadena. El objeto "11" no se crea en el grupo de constantes de cadena, porque se llama al método toString de StringBuilder. La línea ⑥ llama al método interno, porque no hay un objeto "11" en el grupo de constantes de cadena, por lo que llamar al método interno 'definitivamente generará un objeto "11" en el grupo de constantes de cadena. Aquí hay una diferencia de versión,

En jdk6, el grupo de constantes de cadena está en la generación permanente, luego se crea una copia separada de la cadena y el valor se copia en el grupo de constantes de cadena, por lo que la dirección de "11" en este grupo de constantes de cadena y el montón " La dirección de 11 "no es la misma, por lo que devuelve falso

En jdk7, el grupo de constantes de cadena está en el montón. Para ahorrar espacio o mejorar la eficiencia, la máquina virtual Java copiará directamente la dirección del objeto "11" en el montón al "11" en el grupo de constantes de cadena, que es en este momento, "11" en el montón y "11" en el grupo de constantes de cadena son el mismo objeto, por lo que el uso de = es naturalmente el mismo valor

3. Resumen

En jdk1.6, intente poner este objeto de cadena en el grupo de cadenas.

  • ➢Si hay en el grupo de constantes de cadena, no se colocará. Devuelve la dirección del objeto en el grupo de cadenas existente
  • ➢ Si no es así,ObjetoHaga una copia, colóquela en el grupo de cadenas y devuelva la dirección del objeto en el grupo de cadenas

Desde Jdk1.7, intente poner este objeto de cadena en el grupo de cadenas.

  • ➢Si hay en el grupo de constantes de cadena, no se colocará. Devuelve la dirección del objeto en el grupo de cadenas existente
  • ➢ Si no es así, el objetoDirección de referenciaHaga una copia, colóquela en el grupo de cadenas y devuelva la dirección de referencia en el grupo de cadenas
4. Expansión
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();	//intern方法没有什么作用,因为此时字符串常量池中已经有“11”
    System.out.println(s3 == s4);
  • jdk6 debajo de false false
    elefanteDirección de referenciaHaga una copia, colóquela en el grupo de cadenas y devuelva la dirección de referencia en el grupo de cadenas
4. Expansión
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();	//intern方法没有什么作用,因为此时字符串常量池中已经有“11”
    System.out.println(s3 == s4);
  • jdk6 abajofalse false
  • jdk7 abajofalse false

Supongo que te gusta

Origin blog.csdn.net/weixin_44706647/article/details/115184285
Recomendado
Clasificación