Frage

Meine Anwendung wird mit einem intensiven String-Verarbeitung multithreaded. Wir erleben übermäßigen Speicherverbrauch und Profilierung hat gezeigt, dass dies aufgrund der String-Daten. Ich denke, dass der Speicherverbrauch verwenden, eine Art von Fliegengewicht Mustern Implementierung oder sogar Cache stark profitieren würde (was ich sicher weiß, dass Strings wird oft dupliziert, obwohl ich keine harten Daten in dieser Hinsicht).

Ich habe in Java Constant Pool und String.intern sehe, aber es scheint, dass es einige PermGen Probleme hervorrufen kann.

Was wäre die beste Alternative für die Implementierung anwendungsweiten, multithreaded Pool von Strings in Java?

EDIT: Auch meine frühere, ähnliche Frage sehen: Wie funktioniert Java für Fliegengewicht Muster implementieren String unter der Haube

War es hilfreich?

Lösung

Hinweis: Diese Antwort verwendet Beispiele, die nicht in der modernen Runtime JVM Bibliotheken relevant sein könnten. Insbesondere ist das substring Beispiel kein Thema mehr in OpenJDK / Oracle 7 +.

Ich weiß, es geht gegen das, was Menschen, die Sie oft sagen, aber manchmal explizit neue String Erstellen von Instanzen können werden, um eine signifikante Art und Weise Ihr Gedächtnis zu reduzieren.

Weil Strings sind unveränderlich, mehrere Methoden Hebelwirkung dieser Tatsache und die Unterstützung Zeichenfeld teilen Speicher zu speichern. Jedoch gelegentlich kann dies tatsächlich das Gedächtnis erhöht durch die Garbage Collection von nicht verwendeten Teilen dieser Anordnungen zu verhindern.

Zum Beispiel, vorausgesetzt, dass Sie den Nachrichten-IDs einer Protokolldatei Warnung IDs zu extrahieren wurden Parsen. Ihr Code würde wie folgt aussehen:

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

Aber Blick auf die Daten tatsächlich gespeichert werden:

    //...
    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 ist die ganze Testlinie, weil die Matcher gerade eine neue String-Instanz um die gleichen Zeichendaten wickeln. Vergleichen Sie die Ergebnisse, wenn Sie String id = matcher.group(1); mit String id = new String(matcher.group(1)); ersetzen.

Andere Tipps

Dies ist bereits auf der JVM-Ebene. Sie müssen nur sicherstellen, dass Sie nicht new Strings jedes Mal, entweder explizit oder implizit erstellen.

d. nicht tun:

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

Dies würde zwei Instanzen in dem Heap erstellen. Vielmehr tun:

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

Dieses eine Instanz in der Halde schaffen und beide die gleiche beziehen (als Beweis, s1 == s2 kehrt true hier).

Auch += zu verketten Zeichenfolge nicht (in einer Schleife) verwenden:

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

Die += erzeugt implizit eine new String im Heap jedes Mal. Vielmehr tun so

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

Wenn Sie können, eher StringBuilder oder seinen synchronisierten Bruder StringBuffer anstelle von String für „intensive String-Verarbeitung“. Es bietet nützliche Methoden für genau diese Zwecke, wie append(), insert(), delete() usw. Siehe auch seine javadoc .

effeciently Strings packen in Erinnerung! Ich schrieb einmal eine hyperspeichereffiziente Set-Klasse, wo Strings wurden als Baum gespeichert. Wenn ein Blatt durch Verfahren der Buchstaben erreicht war, wurde der Eintrag im Set enthalten. Schneller zur Arbeit mit, auch, und ideal, um ein großes Wörterbuch zu speichern.

Und vergessen Sie nicht, dass die Strings sind oft der größte Teil im Speicher in nahezu jeder App Ich profiliert, so tun sie nicht, wenn Sie sie benötigen.

Abbildung:

Sie haben 3 Strings: Bier, Bohnen und Blut. Sie können eine Baumstruktur wie folgt erstellen:

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

Sehr effizient für z.B. eine Liste der Straßennamen, das ist offensichtlich vernünftigste mit einem festen Wörterbuch, weil Einsatz nicht effizient durchgeführt werden kann. In der Tat soll die Struktur einmal erstellt werden, dann serialisiert und danach nur geladen.

Java 7/8

Wenn Sie das tun, was die akzeptierte Antwort sagt und mit Hilfe von Java 7 oder neuer Sie tun nicht, was sie sagt, Sie sind.

Die Umsetzung der subString() hat sich geändert.

Nie Schreib Code, der auf eine Implementierung beruht, die drastisch verändern und könnte alles noch schlimmer machen, wenn Sie auf das alte Verhalten setzen.

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    }

Wenn Sie also die akzeptierte Antwort mit Java verwenden 7 oder neuer Sie doppelt so viel Speicherverbrauch und Abfall zu schaffen, dass der Bedarf gesammelt werden.

Als erstes entscheiden, wie viel Sie Ihre Anwendung und Entwickler würden leiden, wenn Sie einige dieser Analyse eliminiert. Eine schnellere Anwendung tut Ihnen nicht gut, wenn Sie Ihre Fluktuationsrate im Prozess verdoppeln! Ich denke, auf Ihre Frage basiert können wir annehmen, dass Sie diesen Test bestanden bereits.

Zweitens, wenn Sie nicht beseitigen können ein Objekt erstellen, dann sollte Ihr nächstes Ziel, um sicherzustellen, dass es nicht Eden Sammlung überleben. Und Parse-Lookup kann dieses Problem lösen. Doch „implementiert richtig“ ein Cache (ich mit dieser grundlegenden Prämisse nicht einverstanden, aber ich werde Sie nicht langweilen mit dem begleitenden rant) in der Regel Thread-Konkurrenz bringt. Sie würden für eine andere Art von Speicherdruck ersetzen.

Es gibt eine Variation des Parse-Lookup-Idiom, dass weniger leidet an der Art von Kollateralschäden in der Regel erhalten Sie von Full-on-Caching, und das ist eine einfache vorberechnet Lookup-Tabelle (siehe auch „memoization“). Das Muster Sie in der Regel für diese sehen, ist die Typ Sicher Enumeration (TSE). Mit der TSE, analysieren Sie die Zeichenfolge, geben es an der TSE die zugehörige Aufzählungstyp abzurufen, und dann werfen Sie den String entfernt.

Ist der Text, den Sie Freiform sind die Verarbeitung oder es ist die Eingabe einer starre Spezifikation folgen? Wenn eine Menge Ihres Textes auf einen festen Satz von möglichen Werten macht nach unten, dann könnte ein TSE Ihnen hier hilft und dient eine größere Master: Hinzufügen von Kontext / Semantik zu Ihrer Information an der Stelle der Schöpfung, statt an der Verwendungsstelle .

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top