repetir - Función de deduplicación de cadenas de Java 8
repetir string n veces java (4)
Dado que
String
en Java (como otros lenguajes) consume mucha memoria porque cada carácter consume dos bytes, Java 8 ha introducido una nueva característica llamada
Desduplicación de cadenas
que aprovecha el hecho de que las matrices de caracteres son internas a las cadenas y finales, por lo que JVM puede jugar con ellos.
He leído este ejemplo hasta ahora, pero como no soy un programador profesional de Java, me está costando comprender el concepto.
Aquí está lo que dice:
Se han considerado varias estrategias para la duplicación de cadenas, pero la implementada ahora sigue el siguiente enfoque: cada vez que el recolector de basura visita los objetos de cadena, toma nota de las matrices de caracteres. Toma su valor hash y lo almacena junto con una referencia débil a la matriz. Tan pronto como encuentra otra Cadena que tiene el mismo código hash, las compara char por char. Si coinciden también, se modificará una Cadena y apuntará a la matriz de caracteres de la segunda Cadena. La primera matriz de caracteres ya no se hace referencia y se puede recolectar basura.
Todo este proceso, por supuesto, trae algo de sobrecarga, pero está controlado por límites estrictos. Por ejemplo, si no se encuentra que una cadena tiene duplicados durante un tiempo, ya no se verificará.
Mi primera pregunta
Todavía hay una falta de recursos sobre este tema, ya que se agregó recientemente en la actualización 20 de Java 8, ¿alguien podría compartir algunos ejemplos prácticos sobre cómo ayuda a reducir la memoria consumida por
String
en Java?
Editar:
El enlace de arriba dice:
Tan pronto como encuentre otra cadena que tenga el mismo código hash, las compara char por char
Mi segunda pregunta
Si el código hash de dos
String
es el mismo, las
Strings
ya son las mismas, entonces ¿por qué compararlas
char
por
char
una vez que se descubre que las dos
String
tienen el mismo código hash?
@assylias answer basiclly te dice cómo funciona y es una muy buena respuesta. He probado una aplicación de producción con desduplicación de cadenas y tengo algunos resultados. La aplicación web usa cadenas en gran medida, así que creo que la ventaja es bastante clara.
Para habilitar la desduplicación de cadenas, debe agregar estos parámetros JVM (necesita al menos Java 8u20):
-XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics
El último es opcional pero, como dice el nombre, le muestra las estadísticas de deduplicación de cadenas. Aquí están los míos:
[GC concurrent-string-deduplication, 2893.3K->2672.0B(2890.7K), avg 97.3%, 0.0175148 secs]
[Last Exec: 0.0175148 secs, Idle: 3.2029081 secs, Blocked: 0/0.0000000 secs]
[Inspected: 96613]
[Skipped: 0( 0.0%)]
[Hashed: 96598(100.0%)]
[Known: 2( 0.0%)]
[New: 96611(100.0%) 2893.3K]
[Deduplicated: 96536( 99.9%) 2890.7K( 99.9%)]
[Young: 0( 0.0%) 0.0B( 0.0%)]
[Old: 96536(100.0%) 2890.7K(100.0%)]
[Total Exec: 452/7.6109490 secs, Idle: 452/776.3032184 secs, Blocked: 11/0.0258406 secs]
[Inspected: 27108398]
[Skipped: 0( 0.0%)]
[Hashed: 26828486( 99.0%)]
[Known: 19025( 0.1%)]
[New: 27089373( 99.9%) 823.9M]
[Deduplicated: 26853964( 99.1%) 801.6M( 97.3%)]
[Young: 4732( 0.0%) 171.3K( 0.0%)]
[Old: 26849232(100.0%) 801.4M(100.0%)]
[Table]
[Memory Usage: 2834.7K]
[Size: 65536, Min: 1024, Max: 16777216]
[Entries: 98687, Load: 150.6%, Cached: 415, Added: 252375, Removed: 153688]
[Resize Count: 6, Shrink Threshold: 43690(66.7%), Grow Threshold: 131072(200.0%)]
[Rehash Count: 0, Rehash Threshold: 120, Hash Seed: 0x0]
[Age Threshold: 3]
[Queue]
[Dropped: 0]
Estos son los resultados después de ejecutar la aplicación durante 10 minutos. Como puede ver, la deduplicación de cadenas se ejecutó 452 veces y se "deduplicaron" cadenas de 801.6 MB . La deduplicación de cadenas inspeccionó 27 000 000 cadenas. Cuando comparé mi consumo de memoria de Java 7 con el Parallel GC estándar a Java 8u20 con el G1 GC y habilité la desduplicación de cadenas, el montón cayó aproximadamente un 50% :
Java 7 Parallel GC
Java 8 G1 GC con desduplicación de cadenas
Como su primera pregunta ya ha sido respondida, responderé su segunda pregunta.
Los objetos
String
deben compararse carácter por carácter, porque aunque el
Object
igual s implica hashes iguales, lo inverso
no
es necesariamente cierto.
Como dijo Holger en su comment , esto representa una colisión hash.
Las especificaciones aplicables para el método
hashcode()
son las siguientes:
Si dos objetos son iguales de acuerdo con el método
equals(Object)
, entonces llamar al métodohashCode
en cada uno de los dos objetos debe producir el mismo resultado entero.No es necesario que si dos objetos son desiguales de acuerdo con el método
equals(java.lang.Object)
, llamar al métodohashCode
en cada uno de los dos objetos debe producir resultados enteros distintos. ...
Esto significa que para que puedan garantizar la igualdad, la comparación de cada personaje es necesaria para que puedan confirmar la igualdad de los dos objetos.
Comienzan comparando
hashCode
s en lugar de usar
equals
ya que están usando una tabla hash para las referencias, y esto mejora el rendimiento.
Imagine que tiene una guía telefónica, que contiene personas, que tienen un
String firstName
y un
String lastName
.
Y sucede que en su directorio telefónico, 100,000 personas tienen el mismo
firstName = "John"
.
Debido a que obtiene los datos de una base de datos o un archivo, esas cadenas no están internados, por lo que su memoria JVM contiene la matriz de caracteres
{''J'', ''o'', ''h'', ''n''}
100 mil veces, una por cadena John.
Cada una de estas matrices ocupa, digamos, 20 bytes de memoria, por lo que esos 100k Johns ocupan 2 MB de memoria.
Con la deduplicación, la JVM se dará cuenta de que "John" se duplica muchas veces y hará que todas esas cadenas de John apunten a la misma matriz de caracteres subyacente, disminuyendo el uso de memoria de 2 MB a 20 bytes.
Puede encontrar una explicación más detallada en el JEP . En particular:
Muchas aplicaciones Java a gran escala actualmente tienen cuellos de botella en la memoria. Las mediciones han demostrado que aproximadamente el 25% del conjunto de datos dinámicos de almacenamiento dinámico de Java en este tipo de aplicaciones es consumido por objetos String. Además, aproximadamente la mitad de esos objetos String son duplicados, donde duplicados significa
string1.equals(string2)
es verdadero. Tener objetos String duplicados en el montón es, esencialmente, solo un desperdicio de memoria.[...]
El beneficio esperado real termina en alrededor del 10% de reducción de almacenamiento dinámico. Tenga en cuenta que este número es un promedio calculado basado en una amplia gama de aplicaciones. La reducción del montón para una aplicación específica podría variar significativamente tanto hacia arriba como hacia abajo.
La estrategia que describen es simplemente reutilizar la matriz de caracteres interna de una Cadena en posiblemente muchas Cadenas
equal
.
No es necesario que cada cadena tenga su propia copia si son iguales.
Para determinar más rápidamente si 2 cadenas son iguales, el código hash se usa como primer paso, ya que es una forma rápida de determinar si las cadenas pueden ser iguales. De ahí su declaración:
Tan pronto como encuentre otra cadena que tenga el mismo código hash, las compara char por char
Esto es para hacer una comparación cierta (pero más lenta) para la igualdad una vez que se ha determinado la igualdad usando el código hash.
Al final, cadenas iguales compartirán una única matriz de caracteres subyacente.
Java ha tenido
String.intern()
durante mucho tiempo, para hacer más o menos lo mismo (es decir, ahorrar memoria deduplicando cadenas iguales).
Lo novedoso de esto es que ocurre durante el tiempo de recolección de basura y puede controlarse externamente.