Java コードのパフォーマンスを向上させるためのさまざまなテクニックを共有する

Y2J
リリース: 2017-04-17 11:37:56
オリジナル
1399 人が閲覧しました

[はじめに] Java 6,7,8 String プールの String インターン この記事では、String インターン メソッドが Java 6 でどのように実装されるか、およびこのメソッドが Java 7 および Java 8 Adjustment で何を行うかについて説明します。文字列文字列文字列プール (標準化された文字列の標準化)

Java 6 のこのパラメータは、固定 Permgen メモリ サイズに依然として制限されているため、あまり役に立ちません。以降の説明では Java 6 を直接無視します

String.intern() を使用する場合は、(デフォルトの 1009 と比較して) より大きな -XX:StringTalbeSize 値を設定する必要があります。それ以外の場合はこのメソッドすぐに 0 (プール サイズ) に減ります。 -XX:StringTalbeSize 值(相比较默认的 1009 ),如果你希望更多的使用 String.intern() — 否则这个方法将很快递减到 0 (池大小)。

Java 6,7,8 中的 String.intern – 字符串池

这篇文章将要讨论 Java 6 中是如何实现 String.intern 方法的,以及这个方法在 Java 7 以及 Java 8 中做了哪些调整。

字符串池

字符串池(有名字符串标准化)是通过使用唯一的共享 String 对象来使用相同的值不同的地址表示字符串的过程。你可以使用自己定义的 <a href="//m.sbmmt.com/code/8210.html" target="_blank">Map</a><String, String> (根据需要使用 weak 引用或者 soft 引用)并使用 map 中的值作为标准值来实现这个目标,或者你也可以使用 JDK 提供的 String.intern()

很多标准禁止在 Java 6 中使用 String.intern() 因为如果频繁使用池会市区控制,有很大的几率触发 OutOfMemory<a href="//m.sbmmt.com/wiki/265.html" target="_blank">Exception</a>。Oracle Java 7 对字符串池做了很多改进,你可以通过以下地址进行了解 bugs.sun.com/view_bug.do?bug_id=6962931以及 bugs.sun.com/view_bug.do?bug_id=6962930

Java 6 中的 String.intern()

在美好的过去所有共享的 String 对象都存储在 PermGen 中 — 堆中固定大小的部分主要用于存储加载的类对象和字符串池。除了明确的共享字符串,PermGen 字符串池还包含所有程序中使用过的字符串(这里要注意是使用过的字符串,如果类或者方法从未加载或者被条用,在其中定义的任何常量都不会被加载)

Java 6 中字符串池的最大问题是它的位置 — PermGen。PermGen 的大小是固定的并且在运行时是无法扩展的。你可以使用 -XX:MaxPermSize=N 配置来调整它的大小。据我了解,对于不同的平台默认的 PermGen 大小在 32M 到 96M 之间。你可以扩展它的大小,不过大小使用都是固定的。这个限制需要你在使用 String.intern 时需要非常小心 — 你最好不要使用这个方法 intern 任何无法控制的用户输入。这是为什么在 JAVA6 中大部分使用手动管理 Map 来实现字符串池

Java 7 中的 String.intern()

Java 7 中 Oracle 的工程师对字符串池的逻辑做了很大的改变 — 字符串池的位置被调整到 heap 中了。这意味着你再也不会被固定的内存空间限制了。所有的字符串都保存在堆(heap)中同其他普通对象一样,这使得你在调优应用时仅需要调整堆大小。这 个改动使得我们有足够的理由让我们重新考虑在 Java 7 中使用 String.intern()。

字符串池中的数据会被垃圾收集

没错,在 JVM 字符串池中的所有字符串会被垃圾收集,如果这些值在应用中没有任何引用。这是用于所有版本的 Java,这意味着如果 interned 的字符串在作用域外并且没有任何引用 — 它将会从 JVM 的字符串池中被垃圾收集掉。

因为被重新定位到堆中以及会被垃圾收集,JVM 的字符串池看上去是存放字符串的合适位置,是吗?理论上是 — 违背使用的字符串会从池中收集掉,当外部输入一个字符传且池中存在时可以节省内存。看起来是一个完美的节省内存的策略?在你回答这个之前,可以肯定的是你 需要知道字符串池是如何实现的。

在 Java 6,7,8 中 JVM 字符串池的实现

字符串池是使用一个拥有固定容量的 HashMap

Java 6、7、8 の

String.intern – 文字列プール

🎜この記事では、String.intern メソッドが Java 6 でどのように実装されるか、およびどのような調整が行われるかについて説明します。 Java 7 と Java 8 ではこのメソッドが作られましたか? 🎜

文字列プール

🎜文字列プール (文字列正規化という名前) は、一意の共有 String オブジェクト 🎜 異なるアドレスを持つ同じ値を使用して文字列を表すプロセス。独自に定義した <a href="//m.sbmmt.com/code/8210.html" target="_blank">Map🎜<String, String></a> を使用できます (によれば、弱い引用🎜またはソフトリファレンス)を使用し、マップ内の値を標準値として使用する必要がありますこの目標を達成するには、JDK が提供する String.intern() を使用することもできます。 🎜🎜多くの標準では、Java 6 での String.intern() の使用を禁止しています。これは、プールが頻繁に使用されると市によって制御され、OutOfMemory がトリガーされる可能性が高いためです。 <a href="//m.sbmmt.com/wiki/265.html" target="_blank">例外🎜</a>。 Oracle Java 7 では、文字列プールに多くの改良が加えられています。bugs.sun.com/view_bug.do?bug_id=6962931 および bugs.sun.com/view_bug.do?bug_id=6962930🎜

String.intern について学ぶことができます。 () Java 6

🎜 古き良き時代には、すべての共有 String オブジェクトは PermGen に保存されていました。PermGen は、主にロードされたクラス オブジェクトと文字列プールを保存するために使用されるヒープの固定サイズ部分です。 PermGen 文字列プールには、明示的に共有された文字列に加えて、すべてのプログラムで使用される文字列も含まれています (ここで、クラスまたはメソッドがロードまたは使用されない場合、文字列が使用されることに注意してください。定数 🎜 はロードされません) 🎜🎜 Java 6 の文字列プールに関する最大の問題は、その場所 (PermGen) です。 PermGen のサイズは固定されており、実行時に拡張することはできません。 -XX:MaxPermSize=N 構成を使用してサイズを変更できます。私の知る限り、デフォルトの PermGen サイズの範囲はプラットフォームごとに 32M から 96M です。サイズは拡張できますが、サイズの使用は固定です。この制限により、String.intern を使用する場合は十分に注意する必要があります。制御できないユーザー入力をインターンするためにこのメソッドを使用しない方がよいでしょう。これが、JAVA6 では Map の手動管理のほとんどが文字列プールの実装に使用される理由です🎜

Java 7 の String.intern()

🎜 Java 7 の Oracle エンジニア文字列プールの位置が大幅に変更され、文字列プールの場所がヒープに合わせて調整されました。これは、固定メモリ空間に制限されなくなることを意味します。すべての文字列は他の通常のオブジェクトと同様にヒープに保存されるため、アプリケーションをチューニングするときにのみヒープ サイズを調整できます。この変更により、Java 7 で String.intern() の使用を再考する十分な理由が得られます。 🎜

文字列プール内のデータはガベージ コレクションされます

🎜はい、JVM 文字列プール内のすべての文字列は、これらの値がアプリケーション内で参照を持っていない場合、ガベージ コレクションされます。これは Java のすべてのバージョンで使用されます。つまり、インターンされた文字列がスコープ外で参照がない場合、JVM の文字列プールからガベージ コレクションされます。 🎜🎜ヒープに再配置されてガベージコレクションされるので、JVMの文字列プールが文字列の格納場所として適しているように思えますよね?理論的には、使用法に違反する文字列はプールから収集され、文字が外部から入力され、プールに存在する場合にメモリを節約できます。完璧なメモリ節約戦略のように思えますか?これに答える前に、文字列プーリングがどのように実装されるかを知っておく必要があるのは間違いありません。 🎜

Java 6、7、8 での JVM 文字列プールの実装

🎜 文字列プールは、固定容量の HashMap を使用します。各要素には同じハッシュ値の文字列リストが含まれます。実装の詳細の一部は、Java バグ レポート bugs.sun.com/view_bug.do?bug_id=6962930 から入手できます🎜

デフォルトのプール サイズは 1009 です (上記のバグ レポートのソース コードに表示され、Java7u40 で追加されました)。 JAVA 6 の初期のバージョンでは定数でしたが、その後のバージョンの java6u30 から java6u41 で設定できるように調整されました。 Java 7 では、最初から設定可能です (少なくとも java7u02 では設定可能です)。パラメータ -XX:StringTableSize=N を指定する必要があります。N は文字列プール Map のサイズです。パフォーマンス調整のために事前に設定されたサイズであることを確認してください。 -XX:StringTableSize=N, N 是字符串池 Map 的大小。确保它是为性能调优而预先准备的大小。

在 Java 6 中这个参数没有太多帮助,因为你仍任被限制在固定的 PermGen 内存大小中。后续的讨论将直接忽略 Java 6

Java 7 (直至 Java7u40)

在 Java7 中,换句话说,你被限制在一个更大的堆内存中。这意味着你可以预先设置好 String 池的大小(这个值取决于你的应用程序需求)。通常说来,一旦程序开始内存消耗,内存都是成百兆的增长,在这种情况下,给一个拥有 100 万字符串对象的字符串池分配 8-16M 的内存看起来是比较适合的(不要使用1,000,000 作为 -XX:StringTaleSize 的值 – 它不是质数;使用 1,000,003代替)

你可能期待关于 String 在 Map 中的分配 — 可以阅读我之前关于 HashCode 方法调优的经验。

你必须设置一个更大的 -XX:StringTalbeSize 值(相比较默认的 1009 ),如果你希望更多的使用 String.intern() — 否则这个方法将很快递减到 0 (池大小)。

我没有注意到在 intern 小于 100 字符的字符串时的依赖情况(我认为在一个包含 50 个重复字符的字符串与现实数据并不相似,因此 100 个字符看上去是一个很好的测试限制)

下面是默认池大小的应用程序日志:第一列是已经 intern 的字符串数量,第二列 intern 10,000 个字符串所有的时间(秒)

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
ログイン後にコピー

测试是在 Core i5-3317U@1.7Ghz CPU 设备上进行的。你可以看到,它成线性增长,并且在 JVM 字符串池包含一百万个字符串时,我仍然可以近似每秒 intern 5000 个字符串,这对于在内存中处理大量数据的应用程序来说太慢了。

现在,调整 -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
ログイン後にコピー

可以看到,这时插入字符串的时间近似于常量(在 Map 的字符串列表中平均字符串个数不超过 10 个),下面是相同设置的结果,不过这次我们将向池中插入 1000 万个字符串(这意味着 Map 中的字符串列表平均包含 100 个字符串)

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
ログイン後にコピー

现在让我们将吃的大小增加到 100 万(精确的说是 1,000,003)

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
ログイン後にコピー

如你所看到的,时间非常平均,并且与 “0 到 100万” 的表没有太大差别。甚至在池大小足够大的情况下,我的笔记本也能每秒添加1,000,000个字符对象。

我们还需要手工管理字符串池吗?

现在我们需要对比 JVM 字符串池和 WeakHashMap<String, WeakReference<String>> 它可以用来模拟 JVM 字符串池。下面的方法用来替换 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;
}
ログイン後にコピー

下面针对手工池的相同测试:

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
ログイン後にコピー

当 JVM 有足够内存时,手工编写的池提供了良好的性能。不过不幸的是,我的测试(保留 String.valueOf(0 < N < 1,000,000,000))保留非常短的字符串,在使用 -Xmx1280M 参数时它允许我保留月为 2.5M 的这类字符串。JVM 字符串池 (size=1,000,003)从另一方面讲在 JVM 内存足够时提供了相同的性能特性,知道 JVM 字符串池包含 12.72M 的字符串并消耗掉所有内存(5倍多)。我认为,这非常值得你在你的应用中去掉所有手工字符串池。

在 Java 7u40+ 以及 Java 8 中的 String.intern()

Java7u40 版本扩展了字符串池的大小(这是组要的性能更新)到 60013.这个值允许你在池中包含大约 30000 个独立的字符串。通常来说,这对于需要保存的数据来说已经足够了,你可以通过 -XX:+PrintFlagsFinal JVM 参数获得这个值。

我尝试在原始发布的 Java 8 中运行相同的测试,Java 8 仍然支持 -XX:StringTableSize 参数来兼容 Java 7 特性。主要的区别在于 Java 8 中默认的池大小增加到 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
ログイン後にコピー

测试代码

这篇文章的测试代码很简单,一个方法中循环创建并保留新字符串。你可以测量它保留 10000 个字符串所需要的时间。最好配合 -verbose:gc JVM 参数来运行这个测试,这样可以查看垃圾收集是何时以及如何发生的。另外最好使用 -Xmx

Java 6 では、固定 PermGen メモリ サイズに制限されているため、このパラメータはあまり役に立ちません。これ以降の説明では、Java 6 を直接無視します。🎜

Java 7 (Java7u40 まで)

🎜 Java7 では、言い換えれば、より大きなヒープ メモリに制限されます。これは、文字列プールのサイズを事前に設定できることを意味します (この値はアプリケーションのニーズによって異なります)。一般に、プログラムがメモリを消費し始めると、メモリは数百メガバイトずつ増加します。この場合、100 万個の文字列オブジェクトを含む文字列プールに 8 ~ 16M のメモリを割り当てるほうが適切と思われます (1,000,000 を文字列として使用しないでください)。 -XX:StringTaleSize の値 – これは素数ではありません。代わりに 1,000,003 を使用してください) 🎜🎜 マップ内の文字列の割り当てについて何か期待できるかもしれません - 私の文章を読んでください。以前の投稿 HashCode メソッドのチューニングの経験。 🎜
🎜-XX:StringTalbeSize 値を (デフォルトの 1009 と比較して) 大きく設定する必要があります。もっと String.intern() を使用したい場合は、このメソッドはすぐに 0 (プール サイズ) に減少します。 🎜
🎜 100 文字未満の文字列をインターンするときの依存関係に気づきませんでした (50 文字の繰り返しを含む文字列が実際のデータに似ているとは思えないため、100 文字が適切な制限のように見えます)テスト用) 🎜🎜デフォルトのプール サイズのアプリケーション ログは次のとおりです: 最初の列はインターンされた文字列の数、2 番目の列は常に 10,000 個の文字列をインターンします (秒単位) 🎜
/**
 - 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() );
    }
}
ログイン後にコピー
ログイン後にコピー
🎜 テストは Core で実施されましたi5-3317U@1.7Ghz CPU デバイス。直線的に増加していることがわかります。JVM 文字列プールに 100 万個の文字列が含まれている場合でも、1 秒あたり約 5000 個の文字列をインターンできます。これは、メモリ内で大量のデータを処理するのが遅すぎるアプリケーションには適しています。 🎜🎜ここで、-XX:StringTableSize=100003 パラメータを調整してテストを再実行します。 🎜rrreee🎜 文字列を挿入する時間はほぼ一定であることがわかります (文字列リストの平均)。マップ) 文字列の数は 10 を超えません)。以下は同じ設定の結果ですが、今回は 1,000 万個の文字列をプールに挿入します (これは、マップ内の文字列リストには平均 100 個の文字列が含まれていることを意味します)。 🎜rrreee🎜 次に、食べるサイズを 100 万 (正確には 1,000,003) に増やしてみましょう 🎜rrreee🎜 ご覧のとおり、時間は非常に平均的であり、「0 から 100 万」のテーブルと大きな違いはありません。十分な大きさのプール サイズがあるにもかかわらず、私のノートブックには 1 秒あたり 1,000,000 個の文字オブジェクトが追加されています。 🎜

文字列プールを手動で管理する必要がありますか?

🎜次に、JVM 文字列プールをシミュレートするために使用できる WeakHashMap<String, WeakReference<String>> と JVM 文字列プールを比較する必要があります。次のメソッドは、String.intern を置き換えるために使用されます: 🎜rrreee🎜手書きのプールに対する以下の同じテスト: 🎜rrreee🎜 JVM に十分なメモリがある場合、手書きのプールは良好なパフォーマンスを提供します。残念ながら、私のテスト (String.valueOf(0 < N < 1,000,000,000) を保持) では、-Xmx1280M パラメーターを使用すると非常に短い文字列が保持されます。これにより、2.5M を保持できます。これらの文字列の。一方、JVM 文字列プール (サイズ = 1,000,003) は、JVM メモリが十分な場合、JVM 文字列プールに 12.72M の文字列が含まれ、すべてのメモリを消費する (5 倍) まで、同じパフォーマンス特性を提供します。私の意見では、アプリケーションからすべての手動文字列プーリングを削除する価値は十分にあります。 🎜

Java 7u40+ および Java 8 の String.intern()

🎜Java7u40 バージョンでは、文字列プール サイズが 60013 に拡張されています (これは必須のパフォーマンス更新です)。この値により、約 30,000 個の個別の文字列を含めることができます。通常、保存する必要があるデータにはこれで十分であり、この値は -XX:+PrintFlagsFinal JVM パラメータを介して取得できます。 🎜🎜Java 8 のオリジナル リリースで同じテストを実行してみましたが、Java 7 の機能との互換性を保つために -XX:StringTableSize パラメーターが引き続きサポートされています。主な違いは、Java 8 のデフォルトのプール サイズが 60013 に増加していることです。 🎜rrreee

テスト コード

🎜 この記事のテスト コードは非常に単純で、新しい文字列をループ内で作成して保持します。方法。 10,000 個の文字列を保持するのにかかる時間を測定できます。 -verbose:gc JVM パラメータを使用してこのテストを実行し、ガベージ コレクションがいつどのように発生するかを確認するのが最善です。 -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 参数,当你的应用挂掉时它告诉你字符串池的使用量信息。

以上がJava コードのパフォーマンスを向上させるためのさまざまなテクニックを共有するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!