Question

Mon application est multithread avec le traitement de chaîne intensive. Nous vivons la consommation excessive de mémoire et le profilage a démontré que cela est dû à des données String. Je pense que la consommation de mémoire bénéficierait grandement d'utiliser une sorte de mise en œuvre du modèle poids plume ou même cache (je sais, ce que les chaînes sont souvent dupliquées, bien que je n'ai pas de données précises à cet égard).

Je l'ai regardé Java Constant Pool String.intern, mais il semble que cela peut provoquer des problèmes PermGen.

Quelle serait la meilleure solution pour la mise en œuvre l'application à l'échelle, la piscine multithread de chaînes en Java?

EDIT: Voir aussi mon précédent, question connexe: Comment fonctionne java pour mettre en œuvre modèle poids plume chaîne sous le capot

Était-ce utile?

La solution

Remarque: Cette réponse donne des exemples qui pourraient ne pas être pertinents dans les bibliothèques JVM d'exécution modernes. l'exemple de substring n'est en particulier plus un problème dans OpenJDK / Oracle 7 +.

Je sais qu'il va à l'encontre ce que les gens vous disent souvent, mais parfois créer explicitement de nouvelles instances de String peut être un moyen important pour réduire votre mémoire.

parce que les chaînes sont immuables effet de levier, plusieurs méthodes et fait partager le tableau de caractères de soutien pour sauver la mémoire. Cependant, de temps en temps cela peut effectivement augmenter la mémoire en empêchant la collecte des ordures des parties non utilisées de ces tableaux.

Par exemple, supposons que vous analysez les ID de message d'un fichier journal pour extraire les ID d'avertissement. Votre code ressemblerait à quelque chose comme ceci:

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

Mais regardez les données effectivement stockées:

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

Il est toute la ligne de test, car le matcher enveloppe juste une nouvelle instance de chaîne autour les mêmes données de caractère. Comparez les résultats lorsque vous remplacez String id = matcher.group(1); avec String id = new String(matcher.group(1));.

Autres conseils

Cela se fait déjà au niveau JVM. Vous ne devez vous assurer que vous n'êtes pas en train de créer à chaque fois de new Strings, que ce soit explicitement ou implicitement.

i.e.. ne pas faire:

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

Cela créerait deux cas dans le tas. Plutôt faire:

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

Cela va créer une instance dans le tas et les deux renverra le même (comme preuve, s1 == s2 retournera true ici).

En outre ne pas utiliser += à cordes concaténer (en boucle):

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

Le += crée implicitement un new String dans le chaque tas. Plutôt faire

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

Si vous le pouvez, utilisez plutôt StringBuilder ou son frère StringBuffer synchronisé au lieu de String pour « intensive traitement des chaînes ». Il propose des méthodes utiles pour exactement ces fins, comme append(), insert(), delete(), etc. Voir aussi son javadoc.

emballez effeciently Cordes en mémoire! J'ai écrit une classe Set efficace mémoire hyper, où les chaînes ont été stockés comme un arbre. Si une feuille a été atteint en parcourant les lettres, l'entrée était contenue dans l'ensemble. Rapide à travailler avec, aussi, et idéal pour stocker un grand dictionnaire.

Et ne pas oublier que les chaînes sont souvent la plus grande partie en mémoire dans presque toutes les applications que je moulurés, donc ne se soucient pas pour eux si vous avez besoin d'eux.

Illustration:

Vous avez 3 cordes: bière, des haricots et du sang. Vous pouvez créer une structure arborescente comme ceci:

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

Très efficace pour exemple une liste des noms de rues, ce qui est évidemment le plus raisonnable avec un dictionnaire fixe, car insert ne peut se faire efficacement. En fait, la structure devrait être créée une fois, puis publié en feuilleton et ensuite vient d'être chargé.

Java 7/8

Si vous faites ce que la réponse acceptée dit et en utilisant Java 7 ou plus récent vous ne faites pas ce qu'il dit que vous êtes.

La mise en œuvre de subString() a changé.

Ne jamais écrire du code qui repose sur une mise en œuvre qui peut changer radicalement et pourrait aggraver les choses si vous comptez sur l'ancien comportement.

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    }

Donc, si vous utilisez la réponse acceptée avec Java 7 ou plus récent, vous créez deux fois l'utilisation beaucoup de mémoire et des déchets qui doit recueillir.

Tout d'abord, décider combien votre application et les développeurs souffriraient si l'on éliminait une partie de cette analyse. Une application plus rapide ne vous pas si vous doublez votre taux de rotation des employés dans le processus! Je pense que votre question basé sur on peut supposer que vous avez passé ce test déjà.

Deuxièmement, si vous ne pouvez pas éliminer la création d'un objet, votre prochain objectif devrait être d'assurer qu'il ne survit pas à la collection Eden. Et parse-recherche peut résoudre ce problème. Cependant, un cache « mis en œuvre correctement » (je suis en désaccord avec ce principe de base, mais je ne vais pas vous ennuyer avec la diatribe opératrices) apporte généralement les conflits de fil. Vous seriez remplacerez une sorte de pression de mémoire pour une autre.

Il y a une variation de l'analyse syntaxique-recherche idiome qui souffre moins du genre de dommages collatéraux que vous obtenez habituellement de plein sur la mise en cache, et c'est simple précalculées table de recherche (voir aussi « memoization »). Le modèle que vous voyez habituellement pour c'est le Type Safe Enumeration (TSE). Avec le TSE, vous analysez la chaîne, passez à la Bourse de Toronto pour récupérer le type énuméré associé, et vous jetez la chaîne.

est le texte que vous traitez de forme libre, ou ne l'entrée suivre une spécification rigide? Si beaucoup de votre texte rend à un ensemble fixe de valeurs possibles, puis une TSE pourrait vous aider ici, et sert un maître plus: Ajout contexte / sémantique à vos informations au moment de la création, au lieu d'au point d'utilisation .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top