Domanda

La mia applicazione è multithread con l'elaborazione String intensiva. Stiamo vivendo il consumo eccessivo di memoria e profiling ha dimostrato che questo è a causa di dati String. Credo che il consumo di memoria sarebbe di grande beneficio dall'utilizzo di un qualche tipo di implementazione modello mosca o anche cache (so per certo che le stringhe sono spesso duplicati, anche se io non ho dati certi al riguardo).

Ho guardato Java costante Pool e String.Intern, ma sembra che essa può provocare alcuni problemi PermGen.

Quale sarebbe la migliore alternativa per l'implementazione a livello di applicazione, piscina multithread di stringhe in Java?

EDIT: vedi anche la mia precedente domanda relativa: Come funziona java implementare modello mosca per la stringa sotto il cofano

È stato utile?

Soluzione

Nota: Questo utilizza risposta esempi che potrebbero non essere rilevanti nelle moderne librerie di runtime JVM. In particolare, l'esempio substring non è più un problema nel OpenJDK / Oracle 7 +.

Lo so che va contro ciò che la gente spesso ti dicono, ma a volte la creazione di nuove istanze esplicitamente String possono essere un modo significativo per ridurre la memoria.

A causa stringhe sono immutabili, diversi metodi di leva finanziaria che realtà e condividere l'array di caratteri supporto per risparmiare memoria. Tuttavia, occasionalmente questo può effettivamente aumentare la memoria impedendo garbage collection di parti inutilizzate di tali matrici.

Per esempio, si supponga eri l'analisi del messaggio ID di un file di log per estrarre gli ID di avviso. Il tuo codice sarebbe simile a questa:

//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...
}

Ma un'occhiata ai dati effettivamente memorizzati:

    //...
    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) );

E 'tutta la linea del test, perché il matcher appena avvolge una nuova istanza String intorno lo stesso personaggio data. Confrontare i risultati quando si sostituisce String id = matcher.group(1); con String id = new String(matcher.group(1));.

Altri suggerimenti

Questo è già stato fatto a livello di JVM. Hai solo bisogno di garantire che non si sta creando new Strings ogni volta, esplicitamente o implicitamente.

vale a dire. non fare:

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

Questo creerebbe due istanze nel mucchio. Piuttosto farlo:

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

Questo creerà un caso nel mucchio ed entrambi farà riferimento lo stesso (come prova, s1 == s2 tornerà true qui).

Inoltre non utilizzare += alle stringhe concatenate (in un ciclo):

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

Il += crea implicitamente un new String in ogni mucchio. Piuttosto farlo

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

Se è possibile, piuttosto utilizzare StringBuilder o il suo fratello StringBuffer sincronizzato invece di String per il "trattamento String intensivo". Offre metodi utili per esattamente tali fini, come ad esempio append(), insert(), delete(), ecc Vedere anche la sua javadoc .

effeciently imballare stringhe in memoria! Una volta ho scritto una memoria iper Set efficiente di classe, in cui le stringhe sono stati memorizzati come un albero. Se una foglia è stato raggiunto attraversando le lettere, la voce era contenuta nel set. Veloce al lavoro con, anche, e ideale per memorizzare un dizionario di grandi dimensioni.

E non dimentichiamo che le stringhe sono spesso la parte più grande nella memoria in quasi ogni app che profilate, in modo da non prendersi cura di loro se ne avete bisogno.

Illustrazione:

Hai 3 Archi: birra, fagioli e Sangue. È possibile creare una struttura ad albero come questo:

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

Molto efficiente per esempio un elenco dei nomi delle strade, questo è ovviamente più ragionevole con un dizionario fisso, perché inserto non può essere fatto in modo efficiente. In effetti, la struttura dovrebbe essere creata una volta, poi serializzato e poi appena caricato.

Java 7/8

Se si sta facendo ciò che la risposta accettata dice e utilizzando Java 7 o successiva si sta facendo non è quello che dice che sei.

L'attuazione di subString() è cambiato.

Codice Mai scrittura che si basa su un'implementazione che può cambiare drasticamente e potrebbe peggiorare le cose se si fa affidamento sul vecchio comportamento.

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    }

Quindi, se si utilizza la risposta accettata con Java 7 o successiva che si sta creando il doppio utilizzo della memoria tanto e spazzatura che devono essere raccolti.

In primo luogo, decidere quanto l'applicazione e gli sviluppatori avrebbero sofferto se eliminato alcuni di tale analisi. Un'applicazione più veloce si fa non va bene se si raddoppia il tasso di turnover del personale nel processo! Penso che in base alla tua domanda si può supporre hai superato questo test già.

In secondo luogo, se non è possibile eliminare la creazione di un oggetto, quindi il prossimo obiettivo dovrebbe essere quello di garantire che non sopravvive collezione Eden. E parse-ricerca può risolvere il problema. Tuttavia, una cache "attuato correttamente" (Non sono d'accordo con questa premessa di base, ma non voglio annoiarvi con la declamazione addetto) di solito porta filo contesa. Sareste sostituire un tipo di pressione della memoria per un altro.

C'è una variante del parse-lookup linguaggio che soffre meno dal tipo di danno collaterale di solito si ottiene da full-on caching, e questo è un semplice precalcolata tabella di ricerca (vedi anche "Memoizzazione"). Il modello di solito si vede perché questo è il SICURO tipo Enumeration (TSE). Con la TSE, è analizzare la stringa, passarlo al TSE per recuperare il tipo enumerato associato, e poi si lancia lo String via.

è il testo che stai elaborazione forma libera, o se l'ingresso deve seguire una specifica rigida? Se un sacco di testo rende fino a un insieme fisso di valori possibili, poi una TSE potrebbe aiutare voi qui, e serve un maestro più grande: Aggiunta di contesto / semantica per le informazioni al momento della creazione, invece che nel punto di utilizzo .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top