Maison > Java > javaDidacticiel > Partager diverses techniques pour améliorer les performances du code Java

Partager diverses techniques pour améliorer les performances du code Java

Y2J
Libérer: 2017-04-17 11:37:56
original
1472 Les gens l'ont consulté

[Introduction] String stagiaire dans Java 6,7,8 - String Pool Cet article expliquera comment la méthode String stagiaire est implémentée dans Java 6 et comment cette méthode fonctionne dans Java 7 et quoi. des ajustements ont été effectués dans Java 8. String string string pool (standardisé comme chaîne de nom)

Ce paramètre dans Java 6 n'est pas d'une grande aide, car vous êtes toujours limité dans la taille de mémoire Permgen fixe. La discussion suivante ignorera directement Java 6

Vous devez définir une valeur -XX:StringTalbeSize plus grande (par rapport à la valeur par défaut 1009) si vous souhaitez utiliser String.intern() — sinon ceci. La méthode le réduira rapidement à 0 (taille du pool).

String.intern en Java 6, 7, 8 - String Pool

Cet article expliquera comment la méthode String.intern est implémentée dans Java 6 et comment cette méthode est implémentée Quels ajustements ont été apportés à Java 7 et Java 8.

Regroupement de chaînes

Le regroupement de chaînes (nommé normalisation de chaîne) est effectué en utilisant un String objet partagé unique pour représenter des caractères utilisant la même valeur mais avec des chaînes d'adresses différentes processus. Vous pouvez utiliser votre propre <a href="//m.sbmmt.com/code/8210.html" target="_blank">Map<code><a href="//m.sbmmt.com/code/8210.html" target="_blank">Map</a><String, String> (Utilisez une référence faible ou une référence douce si nécessaire) et utilisez la valeur dans la carte comme valeur standard pour atteindre cet objectif, ou vous pouvez également utiliser le String.intern() fourni par le JDK.

De nombreuses normes interdisent son utilisation dans Java 6String.intern() Car si le pool est utilisé fréquemment, il sera contrôlé par la ville, et il y a de fortes chances de déclencher OutOfMemory<a href="//m.sbmmt.com/wiki/265.html" target="_blank">Exception<code>OutOfMemory<a href="//m.sbmmt.com/wiki/265.html" target="_blank">Exception</a>. Oracle Java 7 a apporté de nombreuses améliorations au pool de chaînes. Vous pouvez en savoir plus sur bugs.sun.com/view_bug.do?bug_id=6962931 et bugs.sun.com/view_bug.do?bug_id=6962930

. String.intern() en Java 6

Au bon vieux temps, tous les objets String partagés étaient stockés dans PermGen — une partie de taille fixe du tas principalement utilisée pour stocker les objets de classe chargés et les pools de chaînes. En plus des chaînes explicitement partagées, le pool de chaînes PermGen contient également des chaînes utilisées dans tous les programmes (notez ici que des chaînes sont utilisées, si la classe ou la méthode n'est jamais chargée ou utilisée, aucune Constantes ne sera chargée)

Le plus gros problème avec le pool de chaînes dans Java 6 est son emplacement : PermGen. La taille de PermGen est fixe et ne peut pas être étendue au moment de l'exécution. Vous pouvez le redimensionner en utilisant la configuration -XX:MaxPermSize=N. Pour autant que je sache, la taille par défaut de PermGen varie de 32 M à 96 M pour différentes plates-formes. Vous pouvez augmenter sa taille, mais l'utilisation de la taille est fixe. Cette limitation vous oblige à être très prudent lorsque vous utilisez String.intern — vous feriez mieux de ne pas utiliser cette méthode pour intégrer une entrée utilisateur incontrôlable. C'est pourquoi la gestion manuelle est principalement utilisée en JAVA6 Map pour implémenter le pool de chaînes

String.intern() en Java 7

Les ingénieurs Oracle en Java 7 ont une compréhension approfondie de la chaîne pool La logique a été considérablement modifiée - l'emplacement du pool de chaînes a été ajusté au tas. Cela signifie que vous n'êtes plus limité par un espace mémoire fixe. Toutes les chaînes sont stockées dans le tas comme les autres objets ordinaires, ce qui vous permet d'ajuster uniquement la taille du tas lors du réglage de votre application. Ce changement nous donne suffisamment de raisons de reconsidérer l'utilisation de String.intern() dans Java 7.

Les données du pool de chaînes seront récupérées

Oui, toutes les chaînes du pool de chaînes JVM seront récupérées, si ces valeurs​​n'ont aucune référence dans l'application . Ceci est utilisé dans toutes les versions de Java, ce qui signifie que si la chaîne interne est hors de portée et n'a aucune référence, elle sera récupérée à partir du pool de chaînes de la JVM.

Puisqu'il est déplacé vers le tas et qu'il sera récupéré, le pool de chaînes de la JVM semble être un endroit approprié pour stocker les chaînes, n'est-ce pas ? Théoriquement, les chaînes qui violent l'utilisation seront collectées à partir du pool, ce qui peut économiser de la mémoire lorsqu'un caractère est saisi de l'extérieur et existe dans le pool. Cela ressemble à une stratégie parfaite pour économiser de la mémoire ? Avant de répondre à cette question, il y a fort à parier que vous devez savoir comment le pooling de chaînes est implémenté.

L'implémentation du pool de chaînes JVM en Java 6, 7, 8

Le pool de chaînes utilise une capacité fixeHashMap Chaque élément contient une chaîne avec la même liste de valeurs de hachage. Certains détails d'implémentation peuvent être obtenus à partir du rapport de bogue Java bugs.sun.com/view_bug.do?bug_id=6962930

La taille du pool par défaut est de 1009 (apparaît dans le code source du rapport de bug mentionné ci-dessus et a été augmentée dans Java7u40). C'était une constante dans les premières versions de JAVA 6 et a ensuite été ajustée pour être configurable de java6u30 à java6u41. Dans Java 7, il est configurable dès le début (du moins il est configurable dans java7u02). Vous devez spécifier le paramètre -XX:StringTableSize=N, N est la taille du pool de chaînes Map. Assurez-vous qu'il s'agit d'une taille pré-dimensionnée pour le réglage des performances.

Dans Java 6, ce paramètre n'est pas d'une grande aide car vous êtes toujours limité à la taille de mémoire PermGen fixe. La discussion suivante ignorera directement Java 6

Java 7 (jusqu'à Java7u40)

En Java7, en d'autres termes, vous êtes limité à un milieu de mémoire de tas plus grand. Cela signifie que vous pouvez prédéfinir la taille du pool de chaînes (cette valeur dépend des besoins de votre application). De manière générale, une fois qu'un programme commence à consommer de la mémoire, la mémoire augmente de centaines de mégaoctets. Dans ce cas, il semble plus approprié d'allouer 8 à 16 Mo de mémoire à un pool de chaînes avec 1 million d'objets chaîne (n'utilisez pas 1 000 000 comme). la valeur de -XX:StringTaleSize – ce n'est pas un nombre premier ; utilisez 1,000,003 à la place)

Vous pourriez vous attendre à quelque chose à propos de l'allocation des chaînes dans Maps — lisez mon expérience précédente avec le réglage de la méthode HashCode.

Vous devez définir une valeur -XX:StringTalbeSize plus grande (par rapport à la valeur par défaut 1009) si vous souhaitez utiliser String.intern() plus souvent - sinon cette méthode sera très rapide. 0 (taille du pool).

Je n'ai pas remarqué la dépendance lors de l'internement de chaînes de moins de 100 caractères (je ne pense pas qu'une chaîne contenant 50 caractères répétés soit similaire aux données réelles, donc 100 caractères semblent comme une bonne limite à tester)

Voici les journaux d'application pour la taille du pool par défaut : la première colonne est le nombre de chaînes qui ont été internes, la deuxième colonne est de 10 000 chaînes tout le temps (secondes)

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
Copier après la connexion

Les tests ont été effectués sur un périphérique CPU Core i5-3317U@1.7Ghz. Vous pouvez voir qu'il augmente de manière linéaire, et avec le pool de chaînes JVM contenant un million de chaînes, je peux toujours interner environ 5 000 chaînes par seconde, ce qui est bon pour une application qui gère beaucoup de données en mémoire. Trop lent.

Maintenant, ajustez les paramètres -XX:StringTableSize=100003 pour réexécuter le test :

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
Copier après la connexion

Comme vous pouvez le voir, le temps d'insertion de la chaîne est approximativement constant (en moyenne sur la liste des chaînes dans la Map Le nombre de chaînes ne dépasse pas 10), ci-dessous est le résultat de la même configuration, mais cette fois nous allons insérer 10 millions de chaînes dans le pool (cela signifie que la liste de chaînes dans la Map contient en moyenne 100 chaînes )

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
Copier après la connexion

Augmentons maintenant la taille du repas à 1 million (1 000 003 pour être précis)

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
Copier après la connexion

Comme vous pouvez le constater, les temps sont très réguliers et cohérents avec « 0 à La montre « 1 million » n’est pas très différente. Même avec une taille de pool suffisamment grande, mon ordinateur portable ajoute 1 000 000 d'objets caractères par seconde.

Devons-nous toujours gérer manuellement le pool de chaînes ?

Nous devons maintenant comparer le pool de chaînes JVM et WeakHashMap<String, WeakReference<String>> qui peuvent être utilisés pour simuler le pool de chaînes JVM. La méthode suivante est utilisée pour remplacer 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;
}
Copier après la connexion

Le même test ci-dessous pour les pools écrits à la main :

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
Copier après la connexion

Les pools écrits à la main offrent de bonnes performances lorsque la JVM a suffisamment de performances de mémoire. Malheureusement, mon test (conservant String.valueOf(0 < N < 1,000,000,000)) retient des chaînes très courtes, et il m'a permis de conserver 2,5 millions de telles chaînes lors de l'utilisation du paramètre -Xmx1280M. Le pool de chaînes JVM (taille = 1 000 003), en revanche, fournit les mêmes caractéristiques de performances lorsque la mémoire JVM est suffisante, jusqu'à ce que le pool de chaînes JVM contienne 12,72 millions de chaînes et consomme toute la mémoire (5 fois plus). À mon avis, cela vaut la peine de supprimer tout regroupement manuel de chaînes de votre application.

String.intern() dans Java 7u40+ et Java 8

La version Java7u40 étend la taille du pool de chaînes (il s'agit d'une mise à jour de performances requise) à 60013. Cette valeur vous permet Le pool contient environ 30 000 cordes uniques. Généralement, cela suffit pour les données qui doivent être enregistrées, et vous pouvez obtenir cette valeur via le paramètre -XX:+PrintFlagsFinal JVM.

J'ai essayé d'exécuter le même test dans la version originale de Java 8, qui prend toujours en charge le paramètre -XX:StringTableSize pour être compatible avec les fonctionnalités de Java 7. La principale différence est que la taille du pool par défaut dans Java 8 a été augmentée à 60013 :

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
Copier après la connexion

Code de test

Le code de test de cet article est très simple, créant et conservant de nouveaux caractères dans une boucle dans une chaîne de méthode. Vous pouvez mesurer le temps nécessaire pour conserver 10 000 chaînes. Il est préférable d'exécuter ce test avec les paramètres -verbose:gc JVM pour voir quand et comment le garbage collection se produit. Il est également préférable d'utiliser le paramètre -Xmx pour appliquer la taille maximale du tas.

这里有两个测试: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() );
    }
}
Copier après la connexion

总结

  • 由于 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 参数,当你的应用挂掉时它告诉你字符串池的使用量信息。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal