Heim  >  Artikel  >  Java  >  Teilen verschiedener Techniken zur Verbesserung der Java-Codeleistung

Teilen verschiedener Techniken zur Verbesserung der Java-Codeleistung

Y2J
Y2JOriginal
2017-04-17 11:37:561443Durchsuche

[Einführung] String intern in Java 6,7,8 – In diesem Artikel wird erläutert, wie die String-intern-Methode in Java 6 implementiert wird und wie diese Methode in Java 7 funktioniert und was In Java 8 wurden Anpassungen vorgenommen. String String String Pool (standardisiert als Namensstring)

Dieser Parameter ist in Java 6 keine große Hilfe, da Sie immer noch auf die feste Permgen-Speichergröße beschränkt sind. In der folgenden Diskussion wird Java 6 direkt ignoriert

Sie müssen einen größeren -XX:StringTalbeSize-Wert festlegen (im Vergleich zum Standardwert 1009), wenn Sie String.intern( ) verwenden möchten – andernfalls diese Methode wird es schnell auf 0 (Poolgröße) reduzieren.

String.intern in Java 6, 7, 8 – String Pool

In diesem Artikel wird erläutert, wie die String.intern-Methode in Java 6 implementiert wird und wie diese Methode implementiert wird Welche Anpassungen wurden in Java 7 und Java 8 vorgenommen?

String-Pooling

String-Pooling (benannte String-Normalisierung) wird durchgeführt, indem ein eindeutiges gemeinsames String Objekt verwendet wird, um Zeichen darzustellen, die denselben Wert, aber unterschiedliche Adressstrings verwenden Verfahren. Sie können Ihre eigene definierte <a href="http://www.php.cn/code/8210.html" target="_blank">Map<code><a href="http://www.php.cn/code/8210.html" target="_blank">Map</a>05ad6303f369fc4ccec4412db2772d1905ad6303f369fc4ccec4412db2772d19 verwenden (Verwenden Sie je nach Bedarf eine schwache -Referenz oder eine weiche Referenz) und verwenden Sie den Wert in der Karte als Standardwert, um dieses Ziel zu erreichen, oder Sie können auch die vom JDK bereitgestellte String.intern() verwenden.

Viele Standards verbieten die Verwendung in Java 6String.intern() Denn wenn der Pool häufig verwendet wird, wird er von der Stadt kontrolliert und es besteht eine hohe Wahrscheinlichkeit, dass er OutOfMemory<a href="ausl%C3%B6st%20http://www.php.cn/wiki/265.html" target="_blank">Ausnahme<code>OutOfMemory<a href="http://www.php.cn/wiki/265.html" target="_blank">Exception</a>. Oracle Java 7 hat viele Verbesserungen am String-Pool vorgenommen. Weitere Informationen finden Sie unter bugs.sun.com/view_bug.do?bug_id=6962931 und bugs.sun.com/view_bug.do?bug_id=6962930

String.intern() in Java 6

In den guten alten Zeiten wurden alle gemeinsam genutzten String-Objekte in PermGen gespeichert – einem Teil des Heaps mit fester Größe, der hauptsächlich zum Speichern geladener Klassenobjekte und String-Pools verwendet wurde. Zusätzlich zu explizit gemeinsam genutzten Zeichenfolgen enthält der PermGen-String-Pool auch Zeichenfolgen, die in allen Programmen verwendet werden (beachten Sie hier, dass Zeichenfolgen verwendet werden. Wenn die Klasse oder Methode nie geladen oder verwendet wird, werden keine Konstanten geladen).

Das größte Problem mit dem String-Pool in Java 6 ist sein Speicherort – PermGen. Die Größe von PermGen ist fest und kann zur Laufzeit nicht erweitert werden. Sie können die Größe mithilfe der -XX:MaxPermSize=N-Konfiguration ändern. Soweit ich weiß, liegt die Standardgröße von PermGen für verschiedene Plattformen zwischen 32 MB und 96 MB. Sie können die Größe erweitern, die Größenverwendung ist jedoch festgelegt. Diese Einschränkung erfordert, dass Sie bei der Verwendung von String.intern sehr vorsichtig sein müssen – Sie sollten diese Methode besser nicht verwenden, um unkontrollierbare Benutzereingaben zu internieren. Aus diesem Grund wird in JAVA6 hauptsächlich die manuelle Verwaltung verwendet Map, um den String-Pool

String.intern() in Java 7 zu implementieren

Oracle-Ingenieure haben in Java 7 einen String-Pool. Die Logik war stark verändert – der Speicherort des String-Pools wurde an den Heap angepasst. Dadurch sind Sie nicht mehr an einen festen Speicherplatz gebunden. Alle Zeichenfolgen werden wie andere gewöhnliche Objekte im Heap gespeichert, sodass Sie die Heap-Größe nur beim Optimieren Ihrer Anwendung anpassen können. Diese Änderung gibt uns Anlass genug, die Verwendung von String.intern() in Java 7 zu überdenken.

Daten im String-Pool werden durch Garbage Collection erfasst

Ja, alle Strings im JVM-String-Pool werden durch Garbage Collection erfasst, wenn diese Werte keine Referenzen in der Anwendung haben . Dies wird in allen Java-Versionen verwendet, was bedeutet, dass, wenn die interne Zeichenfolge außerhalb des Gültigkeitsbereichs liegt und keine Referenz hat, sie aus dem Zeichenfolgenpool der JVM durch Müll gesammelt wird.

Da es auf den Heap verschoben und durch Müll gesammelt wird, scheint der String-Pool der JVM ein geeigneter Ort zum Speichern von Strings zu sein, oder? Theoretisch werden Zeichenfolgen, die gegen die Verwendung verstoßen, aus dem Pool gesammelt, was Speicherplatz sparen kann, wenn ein Zeichen von außen eingegeben wird und im Pool vorhanden ist. Sieht nach einer perfekten Strategie zum Speichersparen aus? Bevor Sie diese Frage beantworten, müssen Sie unbedingt wissen, wie String-Pooling implementiert wird.

Die Implementierung des JVM-String-Pools in Java 6, 7, 8

Der String-Pool verwendet eine feste KapazitätHashMapJedes Element enthält einen String mit derselben Hashwertliste. Einige Implementierungsdetails können dem Java-Fehlerbericht bugs.sun.com/view_bug.do?bug_id=6962930

entnommen werden

Die Standard-Poolgröße beträgt 1009 (erscheint im Quellcode des oben erwähnten Fehlerberichts und wurde in Java7u40 erhöht). Dies war eine Konstante in frühen Versionen von JAVA 6 und wurde später angepasst, um in java6u30 bis java6u41 konfigurierbar zu sein. In Java 7 ist es von Anfang an konfigurierbar (zumindest ist es in Java7u02 konfigurierbar). Sie müssen den Parameter -XX:StringTableSize=N angeben. N ist die Größe des String-Pools Map. Stellen Sie sicher, dass es sich um eine voreingestellte Größe zur Leistungsoptimierung handelt.

In Java 6 ist dieser Parameter keine große Hilfe, da Sie immer noch auf die feste PermGen-Speichergröße beschränkt sind. In der folgenden Diskussion wird Java 6 direkt ignoriert

Java 7 (bis Java7u40)

Mit anderen Worten, in Java7 sind Sie auf eine größere Heap-Speichermitte beschränkt. Dies bedeutet, dass Sie die Größe des String-Pools voreinstellen können (dieser Wert hängt von den Anforderungen Ihrer Anwendung ab). Im Allgemeinen wächst der Speicher um Hunderte von Megabyte, sobald ein Programm beginnt. In diesem Fall scheint es angemessener zu sein, einem String-Pool mit 1 Million String-Objekten 8-16 MB Speicher zuzuweisen (Verwenden Sie nicht 1.000.000 als). der Wert von -XX:StringTaleSize – es ist keine Primzahl; verwenden Sie stattdessen 1,000,003

Sie könnten etwas über die Zuweisung von Strings in Maps erwarten – lesen Sie meine früheren Erfahrungen mit der Optimierung der HashCode-Methode.

Sie müssen einen größeren -Wert festlegen (im Vergleich zum Standardwert 1009), wenn Sie String.intern() häufiger verwenden möchten. Andernfalls wird diese Methode sehr schnell verringert 0 (Poolgröße). -XX:StringTalbeSize

Mir ist die Abhängigkeit beim Internieren von Zeichenfolgen mit weniger als 100 Zeichen nicht aufgefallen (ich glaube nicht, dass eine Zeichenfolge mit 50 wiederholten Zeichen realen Daten ähnelt, also scheinen 100 Zeichen zu sein (wie eine gute Grenze zum Testen)

Hier sind die Anwendungsprotokolle für die Standard-Poolgröße: Die erste Spalte ist die Anzahl der Zeichenfolgen, die interniert wurden, die zweite Spalte enthält ständig 10.000 Zeichenfolgen (Sekunden)

0; time = 0.0 sec
50000; time = 0.03 sec
100000; time = 0.073 sec
150000; time = 0.13 sec
200000; time = 0.196 sec
250000; time = 0.279 sec
300000; time = 0.376 sec
350000; time = 0.471 sec
400000; time = 0.574 sec
450000; time = 0.666 sec
500000; time = 0.755 sec
550000; time = 0.854 sec
600000; time = 0.916 sec
650000; time = 1.006 sec
700000; time = 1.095 sec
750000; time = 1.273 sec
800000; time = 1.248 sec
850000; time = 1.446 sec
900000; time = 1.585 sec
950000; time = 1.635 sec
1000000; time = 1.913 sec
Die Tests wurden auf einem Core i5-3317U@1,7-GHz-CPU-Gerät durchgeführt. Sie können sehen, dass es linear wächst, und da der JVM-String-Pool eine Million Strings enthält, kann ich immer noch etwa 5000 Strings pro Sekunde internieren, was für eine Anwendung, die viele Daten im Speicher verarbeitet, gut ist. Zu langsam.

Passen Sie nun die

-Parameter an, um den Test erneut auszuführen: -XX:StringTableSize=100003

50000; time = 0.017 sec
100000; time = 0.009 sec
150000; time = 0.01 sec
200000; time = 0.009 sec
250000; time = 0.007 sec
300000; time = 0.008 sec
350000; time = 0.009 sec
400000; time = 0.009 sec
450000; time = 0.01 sec
500000; time = 0.013 sec
550000; time = 0.011 sec
600000; time = 0.012 sec
650000; time = 0.015 sec
700000; time = 0.015 sec
750000; time = 0.01 sec
800000; time = 0.01 sec
850000; time = 0.011 sec
900000; time = 0.011 sec
950000; time = 0.012 sec
1000000; time = 0.012 sec
Wie Sie sehen können, ist die Zeit zum Einfügen der Zeichenfolge ungefähr konstant (gemittelt über die Liste). Die Anzahl der Zeichenfolgen in der Karte überschreitet 10 nicht. Unten sehen Sie das Ergebnis des gleichen Setups, aber diesmal werden wir 10 Millionen Zeichenfolgen in den Pool einfügen (dies bedeutet, dass die Zeichenfolgenliste in der Karte durchschnittlich 100 Zeichenfolgen enthält). )

2000000; time = 0.024 sec
3000000; time = 0.028 sec
4000000; time = 0.053 sec
5000000; time = 0.051 sec
6000000; time = 0.034 sec
7000000; time = 0.041 sec
8000000; time = 0.089 sec
9000000; time = 0.111 sec
10000000; time = 0.123 sec
Jetzt erhöhen wir die Essensgröße auf 1 Million (1.000.003 um genau zu sein)

1000000; time = 0.005 sec
2000000; time = 0.005 sec
3000000; time = 0.005 sec
4000000; time = 0.004 sec
5000000; time = 0.004 sec
6000000; time = 0.009 sec
7000000; time = 0.01 sec
8000000; time = 0.009 sec
9000000; time = 0.009 sec
10000000; time = 0.009 sec
Wie Sie sehen können, sind die Zeiten sehr gleichmäßig und konsistent mit „0 bis 1 Million“-Uhr ist nicht viel anders. Selbst bei einem ausreichend großen Pool fügt mein Notebook 1.000.000 Zeichenobjekte pro Sekunde hinzu.

Müssen wir den String-Pool immer noch manuell verwalten?

Jetzt müssen wir den JVM-String-Pool und

vergleichen, der zur Simulation des JVM-String-Pools verwendet werden kann. Die folgende Methode wird verwendet, um WeakHashMap5e31d6cc56a10347ef5ebe29e4b04285> zu ersetzen: String.intern

private static final WeakHashMap<String, WeakReference<String>> s_manualCache = 
    new WeakHashMap<String, WeakReference<String>>( 100000 );

private static String manualIntern( final String str )
{
    final WeakReference<String> cached = s_manualCache.get( str );
    if ( cached != null )
    {
        final String value = cached.get();
        if ( value != null )
            return value;
    }
    s_manualCache.put( str, new WeakReference<String>( str ) );
    return str;
}
Derselbe Test unten für handgeschriebene Pools:

0; manual time = 0.001 sec
50000; manual time = 0.03 sec
100000; manual time = 0.034 sec
150000; manual time = 0.008 sec
200000; manual time = 0.019 sec
250000; manual time = 0.011 sec
300000; manual time = 0.011 sec
350000; manual time = 0.008 sec
400000; manual time = 0.027 sec
450000; manual time = 0.008 sec
500000; manual time = 0.009 sec
550000; manual time = 0.008 sec
600000; manual time = 0.008 sec
650000; manual time = 0.008 sec
700000; manual time = 0.008 sec
750000; manual time = 0.011 sec
800000; manual time = 0.007 sec
850000; manual time = 0.008 sec
900000; manual time = 0.008 sec
950000; manual time = 0.008 sec
1000000; manual time = 0.008 sec
Handgeschriebene Pools bieten eine gute Leistung, wenn die JVM dies getan hat genügend Speicherleistung. Leider behält mein Test (unter Beibehaltung von

) sehr kurze Zeichenfolgen bei und ermöglichte mir, 2,5 Millionen solcher Zeichenfolgen beizubehalten, wenn ich den Parameter String.valueOf(0 < N < 1,000,000,000) verwendete. Der JVM-String-Pool (Größe = 1.000.003) bietet dagegen die gleichen Leistungsmerkmale, wenn der JVM-Speicher ausreichend ist, bis der JVM-String-Pool 12,72 Millionen Strings enthält und den gesamten Speicher verbraucht (fünfmal mehr). Meiner Meinung nach lohnt es sich, das gesamte manuelle String-Pooling aus Ihrer Anwendung zu entfernen. -Xmx1280M

String.intern() in Java 7u40+ und Java 8

Die Java7u40-Version erweitert die String-Pool-Größe (dies ist ein erforderliches Leistungsupdate) auf 60013. Dieser Wert ermöglicht Ihnen, dass der Pool etwa 30.000 enthält einzigartige Saiten. Dies reicht in der Regel für die zu speichernden Daten aus und Sie können diesen Wert über den JVM-Parameter

erhalten. -XX:+PrintFlagsFinal

Ich habe versucht, denselben Test in der Originalversion von Java 8 auszuführen, die immer noch den Parameter

unterstützt, um mit Java 7-Funktionen kompatibel zu sein. Der Hauptunterschied besteht darin, dass die Standardpoolgröße in Java 8 auf 60013 erhöht wurde: -XX:StringTableSize

50000; time = 0.019 sec
100000; time = 0.009 sec
150000; time = 0.009 sec
200000; time = 0.009 sec
250000; time = 0.009 sec
300000; time = 0.009 sec
350000; time = 0.011 sec
400000; time = 0.012 sec
450000; time = 0.01 sec
500000; time = 0.013 sec
550000; time = 0.013 sec
600000; time = 0.014 sec
650000; time = 0.018 sec
700000; time = 0.015 sec
750000; time = 0.029 sec
800000; time = 0.018 sec
850000; time = 0.02 sec
900000; time = 0.017 sec
950000; time = 0.018 sec
1000000; time = 0.021 sec
Testcode

Der Testcode für diesen Artikel ist sehr einfach und erstellt und behält neue Zeichen bei in einer Schleife in einer Methodenzeichenfolge. Sie können messen, wie lange es dauert, 10.000 Zeichenfolgen aufzubewahren. Führen Sie diesen Test am besten mit den

JVM-Parametern aus, um zu sehen, wann und wie die Garbage Collection erfolgt. Außerdem ist es besser, den Parameter -verbose:gc zu verwenden, um die maximale Größe des Heaps zu erzwingen. -Xmx

这里有两个测试:testStringPoolGarbageCollection 将显示 JVM 字符串池被垃圾收集 — 检查垃圾收集日志消息。在 Java 6 的默认 PermGen 大小配置上,这个测试会失败,因此最好增加这个值,或者更新测试方法,或者使用 Java 7.

第二个测试显示内存中保留了多少字符串。在 Java 6 中执行需要两个不同的内存配置 比如: -Xmx128M 以及 -Xmx1280M (10 倍以上)。你可能发现这个值不会影响放入池中字符串的数量。另一方面,在 Java 7 中你能够在堆中填满你的字符串。

/**
 - Testing String.intern.
 *
 - Run this class at least with -verbose:gc JVM parameter.
 */
public class InternTest {
    public static void main( String[] args ) {
        testStringPoolGarbageCollection();
        testLongLoop();
    }

    /**
     - Use this method to see where interned strings are stored
     - and how many of them can you fit for the given heap size.
     */
    private static void testLongLoop()
    {
        test( 1000 * 1000 * 1000 );
        //uncomment the following line to see the hand-written cache performance
        //testManual( 1000 * 1000 * 1000 );
    }

    /**
     - Use this method to check that not used interned strings are garbage collected.
     */
    private static void testStringPoolGarbageCollection()
    {
        //first method call - use it as a reference
        test( 1000 * 1000 );
        //we are going to clean the cache here.
        System.gc();
        //check the memory consumption and how long does it take to intern strings
        //in the second method call.
        test( 1000 * 1000 );
    }

    private static void test( final int cnt )
    {
        final List<String> lst = new ArrayList<String>( 100 );
        long start = System.currentTimeMillis();
        for ( int i = 0; i < cnt; ++i )
        {
            final String str = "Very long test string, which tells you about something " +
            "very-very important, definitely deserving to be interned #" + i;
//uncomment the following line to test dependency from string length
//            final String str = Integer.toString( i );
            lst.add( str.intern() );
            if ( i % 10000 == 0 )
            {
                System.out.println( i + "; time = " + ( System.currentTimeMillis() - start ) / 1000.0 + " sec" );
                start = System.currentTimeMillis();
            }
        }
        System.out.println( "Total length = " + lst.size() );
    }

    private static final WeakHashMap<String, WeakReference<String>> s_manualCache =
        new WeakHashMap<String, WeakReference<String>>( 100000 );

    private static String manualIntern( final String str )
    {
        final WeakReference<String> cached = s_manualCache.get( str );
        if ( cached != null )
        {
            final String value = cached.get();
            if ( value != null )
                return value;
        }
        s_manualCache.put( str, new WeakReference<String>( str ) );
        return str;
    }

    private static void testManual( final int cnt )
    {
        final List<String> lst = new ArrayList<String>( 100 );
        long start = System.currentTimeMillis();
        for ( int i = 0; i < cnt; ++i )
        {
            final String str = "Very long test string, which tells you about something " +
                "very-very important, definitely deserving to be interned #" + i;
            lst.add( manualIntern( str ) );
            if ( i % 10000 == 0 )
            {
                System.out.println( i + "; manual time = " + ( System.currentTimeMillis() - start ) / 1000.0 + " sec" );
                start = System.currentTimeMillis();
            }
        }
        System.out.println( "Total length = " + lst.size() );
    }
}

总结

  • 由于 Java 6 中使用固定的内存大小(PermGen)因此不要使用 String.intern() 方法。

  • Java7 和 8 在堆内存中实现字符串池。这以为这字符串池的内存限制等于应用程序的内存限制。

  • 在 Java 7 和 8 中使用 -XX:StringTableSize 来设置字符串池 Map 的大小。它是固定的,因为它使用 HashMap 实现。近似于你应用单独的字符串个数(你希望保留的)并且设置池的大小为最接近的质数并乘以 2 (减少碰撞的可能性)。它是的 String.intern 可以使用相同(固定)的时间并且在每次插入时消耗更小的内存(同样的任务,使用java WeakHashMap将消耗4-5倍的内存)。

  • 在 Java 6 和 7(Java7u40以前) 中 -XX:StringTableSize 参数的值是 1009。Java7u40 以后这个值调整为 60013 (Java 8 中使用相同的值)。

  • 如果你不确定字符串池的用量,参考:-XX:+PrintStringTableStatistics JVM 参数,当你的应用挂掉时它告诉你字符串池的使用量信息。

Das obige ist der detaillierte Inhalt vonTeilen verschiedener Techniken zur Verbesserung der Java-Codeleistung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn