Pregunta

Mi aplicación es multiproceso con procesamiento intensivo de cadena. Estamos experimentando el consumo excesivo de memoria y perfilado ha demostrado que esto es debido a los datos de cadena. Creo que el consumo de memoria se beneficiaría enormemente del uso de algún tipo de aplicación flyweight o incluso caché (I saber con certeza que las cadenas son a menudo se repite, aunque yo no tengo datos concretos al respecto).

He mirado constante de Java piscina y String.intern, pero parece que puede provocar algunos problemas PermGen.

¿Cuál sería la mejor alternativa para la implementación de aplicaciones de todo, la piscina multiproceso de cadenas en Java?

EDIT: También ver a mi anterior pregunta relacionada:? ¿Cómo implementar Java flyweight para la cadena bajo el capó

¿Fue útil?

Solución

Nota: Este sistema utiliza ejemplos de respuestas que podrían no ser relevante en las bibliotecas de tiempo de ejecución de JVM modernas. En particular, el ejemplo substring ya no es un problema en OpenJDK / Oracle 7 +.

Sé que va en contra de lo que la gente a menudo le dicen, pero a veces de manera explícita la creación de nuevas instancias String puede ser una manera significativa a reducir su memoria.

Debido a que las cadenas son inmutables, varios métodos de apalancamiento ese hecho y compartir el arreglo de caracteres respaldo para ahorrar memoria. Sin embargo, de vez en cuando en realidad esto puede aumentar la memoria mediante la prevención de la recolección de basura de piezas disponibles de dichas matrices.

Por ejemplo, supongamos que estaba analizando el ID de mensaje de un archivo de registro para extraer los ID de alerta. El código sería algo como esto:

//Format:
//ID: [WARNING|ERROR|DEBUG] Message...
String testLine = "5AB729: WARNING Some really really really long message";

Matcher matcher = Pattern.compile("([A-Z0-9]*): WARNING.*").matcher(testLine);
if ( matcher.matches() ) {
    String id = matcher.group(1);
        //...do something with id...
}

Pero vistazo a los datos realmente va a guardar:

    //...
    String id = matcher.group(1);
    Field valueField = String.class.getDeclaredField("value");
    valueField.setAccessible(true);

    char[] data = ((char[])valueField.get(id));
    System.out.println("Actual data stored for string \"" + id + "\": " + Arrays.toString(data) );

Es toda la línea de prueba, ya que el matcher simplemente envuelve una nueva instancia de cuerda alrededor de la misma datos de carácter. Comparación de los resultados cuando se reemplaza con String id = matcher.group(1); String id = new String(matcher.group(1));.

Otros consejos

Esto ya se hace en el nivel JVM. Sólo tiene que asegurarse de que no está creando new Strings cada vez, ya sea explícita o implícitamente.

es decir. no lo haga:

String s1 = new String("foo");
String s2 = new String("foo");

Esto crearía dos instancias en el montón. En lugar de hacerlo:

String s1 = "foo";
String s2 = "foo";

Esto creará un caso en el montón y ambos se referirá la misma (como se evidencia, s1 == s2 volverá true aquí).

también no utilice += a cadenas concatenar (en un bucle):

String s = "";
for (/* some loop condition */) {
    s += "new";
}

El += crea implícitamente un new String en el cada montón. En lugar de hacerlo

StringBuilder sb = new StringBuilder();
for (/* some loop condition */) {
    sb.append("new");
}
String s = sb.toString();

Si es posible, en lugar utilizar StringBuilder o su hermano StringBuffer sincronizada en lugar de String para el "proceso intensivo de cuerdas". Ofrece métodos útiles para exactamente aquellos fines, como append(), insert(), delete(), etc. También vea su javadoc .

eficientemente empacar las cadenas en la memoria! Una vez escribí una clase conjunto eficiente de memoria hiper, donde Cuerdas se almacena como un árbol. Si una hoja se alcanza mediante el desplazamiento de las letras, la entrada estaba contenida en el conjunto. Rápido de trabajar, también, y es ideal para almacenar un diccionario general.

Y no se olvide que las cadenas son a menudo la parte más grande de la memoria en casi todas las aplicaciones que perfilado, por lo que no se preocupan por ellos, si los necesita.

Ejemplo:

Usted tiene 3 Cuerdas: cerveza, frijoles y sangre. Puede crear una estructura de árbol como esto:

B
+-e
  +-er
  +-ans
+-lood

muy eficiente para, por ejemplo, una lista de los nombres de las calles, esto es obviamente más razonable con un diccionario fijo, porque inserto no se puede hacer de manera eficiente. De hecho, la estructura debe crearse una vez, luego serializado y después acaba de cargar.

Java 7/8

Si usted está haciendo lo que dice la respuesta aceptada y el uso de Java 7 o superior que no están haciendo lo que dice que eres.

La implementación de subString() ha cambiado.

Nunca código de escritura que se basa en una aplicación que puede cambiar drásticamente y podría empeorar las cosas si usted está confiando en el comportamiento anterior.

1950    public String substring(int beginIndex, int endIndex) {
1951        if (beginIndex < 0) {
1952            throw new StringIndexOutOfBoundsException(beginIndex);
1953        }
1954        if (endIndex > count) {
1955            throw new StringIndexOutOfBoundsException(endIndex);
1956        }
1957        if (beginIndex > endIndex) {
1958            throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
1959        }
1960        return ((beginIndex == 0) && (endIndex == count)) ? this :
1961            new String(offset + beginIndex, endIndex - beginIndex, value);
1962    }

Así que si usa la respuesta aceptada con Java 7 o más reciente que está creando el doble uso de la memoria y mucho más basura que debe recogerse.

En primer lugar, decidir cuánto de su aplicación y desarrolladores sufrirían si usted eliminó algo de ese análisis. Una aplicación más rápido le hace ningún bien si se duplica la tasa de rotación de empleados en el proceso! Creo que en base a su pregunta, podemos suponer que ha pasado esta prueba ya.

En segundo lugar, si no se puede eliminar la creación de un objeto, entonces su siguiente objetivo debe ser garantizar que no sobrevive colección Edén. Y de análisis sintáctico-lookup puede resolver ese problema. Sin embargo, una caché "correcta ejecución" (estoy de acuerdo con esa premisa básica, pero no voy a aburrir con la diatriba auxiliar) por lo general trae discordia hilo. Usted sería sustituir un tipo de presión de memoria para otro.

Hay una variación del análisis sintáctico-lookup modismo que sufre menos el tipo de daño colateral que por lo general reciben de lleno-en el almacenamiento en caché, y eso es una sencilla tabla de consulta precalculada (véase también "memoization"). El patrón se ve generalmente para esto es el Tipo de seguridad enumeración (TSE). Con el TSE, que analizar la cadena, lo pasa al TSE para recuperar el tipo enumerado asociado, y luego tirar la cuerda de distancia.

Es el texto que está procesando de forma libre, o se comporta la entrada tiene que seguir una especificación rígida? Si una gran cantidad de su texto hace a un conjunto fijo de valores posibles, a continuación, una EET podría ayudar aquí, y sirve un maestro mayor: Adición de contexto / la semántica de la información en el punto de la creación, en lugar de en el punto de uso .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top