2011-12-08 21 views
7

Esta es una pregunta que hemos tenido problemas para entender. Es complicado describirlo usando texto, pero espero que se comprenda la esencia.Tamaño de pila retenido de una cadena en java

Entiendo que el contenido real de una cadena se incluye en una matriz de caracteres interna. En instancias normales, el tamaño de almacenamiento retenido de la cadena incluirá 40 bytes más el tamaño de la matriz de caracteres. Esto se explica here. Al llamar a una subcadena, la matriz de caracteres conserva una referencia a la cadena original y, por lo tanto, el tamaño retenido de la matriz de caracteres podría ser mucho mayor que la cadena misma.

Sin embargo, al perfilar el uso de la memoria con Yourkit o MAT algo extraño parece suceder. La cadena que hace referencia al tamaño retenido de la matriz char no incluye el tamaño retenido de la matriz de caracteres.

Un ejemplo podría ser el siguiente (semi pseudo-código):

String date = "2011-11-33"; (24 bytes) 
date.value = char{1172}; (2360 bytes) 

tamaño conservado de la cadena se define como 24 bytes, sin incluir el tamaño de retenido de la matriz de caracteres. Esto podría tener sentido si hay muchas referencias a la matriz de caracteres debido a muchas operaciones de subcadenas.

Ahora, cuando esta cadena se incluye en algún tipo de colección, como una matriz o lista, el tamaño retenido de esta matriz incluirá el tamaño retenido de todas las cadenas, incluido el tamaño retenido de la matriz de caracteres.

entonces tenemos una situación como esta:

Array's retained size = 300 bytes 
array[0] = String 40 bytes; 
array[1] = String 40 bytes; 
array[1].value = char[] (220 bytes) 

Es, por tanto, tiene que mirar en cada entrada de la matriz a tratar de llegar a donde el tamaño retenido viene.

De nuevo, esto se puede explicar porque la matriz contiene todas las cadenas que mantienen referencias a la misma matriz de caracteres y, por lo tanto, el tamaño retenido de la matriz es correcto.

Ahora llegamos al problema.

Guardo en un objeto separado una referencia a la matriz que mencioné anteriormente, así como una matriz diferente con las mismas cadenas. En ambas matrices, las cadenas se refieren a la misma matriz de caracteres. Esto es esperado, después de todo, estamos hablando de la misma cadena. Sin embargo, el tamaño retenido de este conjunto de caracteres se cuenta para ambas matrices en este nuevo objeto. En otras palabras, el tamaño retenido parece ser el doble. Si elimino la primera matriz, la segunda matriz aún tendrá una referencia a la matriz de caracteres y viceversa. Esto causa una confusión en el sentido de que parece que Java contiene dos referencias separadas para la misma matriz de caracteres. ¿Cómo puede ser esto? ¿Es esto un problema con la memoria de Java o es solo la forma en que los perfiladores muestran información?

Este problema nos causó muchos dolores de cabeza al intentar rastrear el gran uso de memoria en nuestra aplicación.

Nuevamente - Espero que alguien allí pueda entender la pregunta y explicarla.

Gracias por su ayuda

Respuesta

4

Guardo en un objeto separado una referencia a la matriz que mencioné anteriormente, así como una matriz diferente con las mismas cadenas. En ambas matrices, las cadenas se refieren a la misma matriz de caracteres. Esto es esperado, después de todo, estamos hablando de la misma cadena. Sin embargo, el tamaño retenido de este conjunto de caracteres se cuenta para ambas matrices en este nuevo objeto. En otras palabras, el tamaño retenido parece ser el doble.

Lo que tenemos aquí es una referencia transitiva en un árbol dominador:

enter image description here

La matriz de caracteres no debe aparecer en el tamaño conservado de cualquiera de matriz. Si el generador de perfiles lo muestra de esa manera, entonces eso es engañoso.

Esta es la forma JProfiler muestra esta situación en los mayores objetos Vista:

enter image description here

La instancia de cadena que está contenido en ambas matrices, se muestra fuera de los casos de matriz, con una etiqueta [referencia transitivo] . Si desea explorar los caminos reales, se puede añadir el soporte de la matriz y la cadena a la gráfica y encontrar todos los caminos entre ellos:

enter image description here

responsabilidad: Mi empresa desarrolla JProfiler.

+0

Voy a descargar la evaluación de jprofiler para ver si tiene más sentido. Gracias por tu respuesta. Parece tener más sentido ... – slbruce

+0

Lamentablemente, encontré jprofiler muy difícil de usar. No tengo tiempo para aprender cómo usarlo en todo su potencial, así que simplemente tomaré su palabra :) Gracias por su ayuda – slbruce

+0

Como muestra de su agradecimiento, podría aceptar mi respuesta :-) Y déjame le aseguro que JProfiler no es difícil de usar en absoluto. Para el ejemplo anterior, solo toma una instantánea de montón, selecciona la clase que contiene las matrices y activa la vista de "objetos más grandes". –

0

A menos que las cadenas están internados, pueden ser equal() pero no ==. Al construir un objeto String a partir de una matriz char, el constructor realizará una copia de la matriz char. (Esta es la única forma de proteger la cadena inmutable de los cambios posteriores en los valores de la matriz char.)

+0

Creo que estaba hablando de las dos matrices que tienen exactamente las mismas instancias String. – Thilo

+0

@Thilo - Estaba retomando _ "En ambas matrices, las cadenas hacen referencia a la misma matriz de caracteres". _ Es difícil garantizar que no se formen las cadenas. –

+0

En realidad, es trivial para garantizar eso. 'String s2 = s1.substring (0)' Tiene razón, el nuevo constructor String (char []) copiará la matriz char. sin embargo, el nuevo constructor String (String) se comportará de manera diferente en IBM JVM que en Sun JVM. –

3

Yo diría que es solo la forma en que el generador de perfiles muestra la información. No tiene idea de que las dos matrices deberían considerarse para la "deduplicación". ¿Qué tal si envuelves las dos matrices en algún tipo de objeto simulado y ejecutas tu perfil contra eso? Entonces, debería ser capaz de encargarse del "doble conteo".

+0

Acepto ... profiler probablemente esté contando las matrices internas de la cadena dos veces. –

+0

Yo tendería a estar de acuerdo, sin embargo, este problema parece causar que ocurra el gc completo cuando podría no ser necesario, en otras palabras, incluso Java lo ve de esta manera – slbruce

+0

Así que estás diciendo que Java está confundido sobre cuánto espacio de montón se usa y ¿cuánto es gratis (y cuenta el mismo objeto dos veces)? Eso parece poco probable ... – Thilo

0

Si se ejecuta con -XX:-UseTLAB

public static void main(String... args) throws Exception { 
    StringBuilder text = new StringBuilder(); 
    text.append(new char[1024]); 
    long free1 = free(); 
    String str = text.toString(); 
    long free2 = free(); 
    String [] array = { str.substring(0, 100), str.substring(101, 200) }; 
    long free3 = free(); 
    if (free3 == free2) 
     System.err.println("You must use -XX:-UseTLAB"); 
    System.out.println("To create String with 1024 chars "+(free1-free2)+" bytes\nand to create an array with two sub-string was "+(free2-free3)); 
} 

private static long free() { 
    return Runtime.getRuntime().freeMemory(); 
} 

impresiones

To create String with 1024 chars 2096 bytes 
and to create an array with two sub-string was 88 

Se puede ver su mayor consumo de memoria que se puede esperar si compartían la misma tienda back-end.

Si observa el código en la clase String.

public String substring(int start, int end) { 
    // checks. 
    return ((beginIndex == 0) && (endIndex == count)) ? this : 
     new String(offset + beginIndex, endIndex - beginIndex, value); 
} 

String(int offset, int count, char value[]) { 
    this.value = value; 
    this.offset = offset; 
    this.count = count; 
} 

Se puede ver que subcadena para cadena no tiene una copia de la matriz de valores subyacente.


Otro aspecto a considerar es el -XX:+UseCompressedStrings que está activada de forma predeterminada en las versiones más recientes de la JVM. Esto alienta a la JVM a utilizar byte [] en lugar de char [] cuando sea posible.

El tamaño de los encabezados para el objeto String y array varía para JVM de 32 bits, JVM de 64 bits con referencias de 32 bits y JVM de 64 bits con referencias de 64 bits.

+3

No sé dónde ha encontrado esa implementación de subcadena, pero en Oracle/Sun e IBM JVM, la subcadena NO copiará la matriz. –

+0

¡Hay un error en mi código! La subcadena es de StringBuilder, que debe tomar una copia. –

+0

De acuerdo. Definitivamente este no es el comportamiento que veo – slbruce

Cuestiones relacionadas