如何理解字符串常量池

這篇文章主要講解了“如何理解字符串常量池”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何理解字符串常量池”吧!

創(chuàng)新互聯(lián)建站2013年開創(chuàng)至今,先為承德等服務(wù)建站,承德等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為承德企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

字符串冗余問題

從平均情況來看,應(yīng)用程序中的String對(duì)象會(huì)消耗大量的內(nèi)存。這里面有一部分是冗余的——同樣的字符串會(huì)存在多個(gè)不同的實(shí)例(a != b, 但a.equals(b))。在實(shí)踐中,有許多字符串會(huì)出于不同的原因造成冗余。

最初JDK提供了一個(gè)String.intern()方法來解決字符串冗余的問題。這個(gè)方法的缺點(diǎn)在于你必須得去找出哪些字符串需要進(jìn)行駐留(interned)。通常都需要具備冗余字符串查找功能的堆分析的工具才行,比如Youkit profiler。如果使用得當(dāng)?shù)脑挘址v留會(huì)是一個(gè)非常有效的節(jié)省內(nèi)存的工具——它讓你可以重用整個(gè)字符串對(duì)象(每個(gè)字符串對(duì)象在底層char[]的基礎(chǔ)上會(huì)增加24字節(jié)的額外開銷)。

Java 7 update 6開始,每個(gè)String對(duì)象都有一個(gè)自己專屬的私有char[] 。這樣JVM才可以自動(dòng)進(jìn)行優(yōu)化——既然底層的char[]沒有暴露給外部的客戶端的話,那么JVM就能去判斷兩個(gè)字符串的內(nèi)容是否是一致的,進(jìn)而將一個(gè)字符串底層的char[]替換成另一個(gè)字符串的底層char[]數(shù)組

字符串去重這個(gè)特性就是用來做這個(gè)的,它在Java 8 update 20中被引入。下面是它的工作原理:

  • 1.你得使用G1垃圾回收器并啟用這一特性 -XX:+UseG1GC -XX:+UseStringDeduplication這一特性作為G1垃圾回收器的一個(gè)可選的步驟來實(shí)現(xiàn)的,如果你用的是別的回收器是無法使用這一特性的。

  • 2.這個(gè)特性會(huì)在G1回收器的minor GC階段中執(zhí)行。根據(jù)我的觀察來看,它是否會(huì)執(zhí)行取決于有多少空閑的CPU周期。因此,你不要指望它會(huì)在一個(gè)處理本地?cái)?shù)據(jù)的數(shù)據(jù)分析器中會(huì)被執(zhí)行,也就是說,WEB服務(wù)器中倒是很可能會(huì)執(zhí)行這個(gè)優(yōu)化。

  • 3.字符串去重會(huì)去查找那些未被處理的字符串,計(jì)算它們的hash值(如果它沒在應(yīng)用的代碼中被計(jì)算過的話),然后再看是否有別的字符串的hash值和底層的char[]都是一樣的。如果找到的話——它會(huì)用一個(gè)新字符串的char[]來替換掉現(xiàn)有的這個(gè)char[]。

  • 4.字符串去重只會(huì)去處理那些歷經(jīng)數(shù)次GC仍然存活的那些字符串這樣能確保大多數(shù)的那些短生命周期的字符串不會(huì)被處理。字符串的這個(gè)最小的存活年齡可以通過 -XX:StringDeduplicationAgeThreshold=3的JVM參數(shù)來指定(3是這個(gè)參數(shù)的默認(rèn)值)。


下面是這個(gè)實(shí)現(xiàn)的一些重要的結(jié)論:

 沒錯(cuò),如果你想享受字符串去重特性的這份免費(fèi)午餐的話,你得使用G1回收器。

使用parellel GC的話是無法使用它的,而對(duì)那些對(duì)吞吐量要求比延遲時(shí)期高的應(yīng)用而言,parellel GC應(yīng)該是個(gè)更好的選擇。

字符串去重是無法在一個(gè)已加載完的系統(tǒng)中運(yùn)行的。要想知道它是否被執(zhí)行了,可以通過 -XX:+PrintStringDeduplicationStatistics參數(shù)來運(yùn)行JVM,并查看控制臺(tái)的輸出。

如果你希望節(jié)省內(nèi)存的話,你可以在應(yīng)用程序中將字符串進(jìn)行駐留(interned)——那么放手去做吧,不要依賴于字符串去重的功能。

你需要時(shí)刻注意的是字符串去重是要處理你所有的字符串的(至少是大部分吧)——也就是說盡管你知道某個(gè)指定的字符串的內(nèi)容是唯一的(比如說GUID),但JVM并不知道這些,它還是會(huì)嘗試將這個(gè)字符串和其它的字符串進(jìn)行匹配。這樣的結(jié)果就是,字符串去重所產(chǎn)生的CPU開銷既取決于堆中字符串的數(shù)量(將新的字符串和別的字符串進(jìn)行比較),也取決于你在字符串去重的間隔中所創(chuàng)建的字符串的數(shù)量(這些字符串會(huì)和堆中的字符串進(jìn)行比較)。

在一個(gè)擁有好幾個(gè)G的堆的JVM上,可以通過- XX:+PrintStringDeduplicationStatistics選項(xiàng)來看下這個(gè)特性所產(chǎn)生的影響究竟有多大。

另一方面,它基本是以一種非阻塞的方式來完成的,如果你的服務(wù)器有足夠多的空閑CPU的話,那為什么不用呢?

最后,請(qǐng)記住,String.intern可以讓你只針對(duì)你的應(yīng)用程序中指定的某一部分已知會(huì)產(chǎn)生冗余的字符串。通常來說,它只需要比較一個(gè)較小的駐留字符串的池就可以了,也就是說你可以更高效地使用你的CPU。不僅如此,你還可以將整個(gè)字符串對(duì)象進(jìn)行駐留,這樣每個(gè)字符串你還多節(jié)省了24個(gè)字節(jié)。

這里是我用來試驗(yàn)這一特性的一個(gè)測(cè)試類。這三個(gè)測(cè)試都會(huì)一直運(yùn)行到JVM拋出OOM為止,因此你得分別去單獨(dú)地運(yùn)行它們。

第一個(gè)測(cè)試會(huì)創(chuàng)建內(nèi)容一樣的字符串,如果你想知道當(dāng)堆中字符串很多的時(shí)候,字符串去重會(huì)花掉多少時(shí)間的話,這個(gè)測(cè)試就變得非常有用了。盡量給第一個(gè)測(cè)試分配盡可能多的內(nèi)存——它創(chuàng)建的字符串越多,優(yōu)化的效果就越好。

第二三個(gè)測(cè)試會(huì)比較去重(第二個(gè)測(cè)試)及駐留(interning, 第三個(gè)測(cè)試)間的差別。你得用一個(gè)相同的Xmx設(shè)置來運(yùn)行它們。在程序中我把這個(gè)常量設(shè)置成了Xmx256M,但是當(dāng)然了,你可以分配得多點(diǎn)。然而,你會(huì)發(fā)現(xiàn),和interning測(cè)試相比,去重測(cè)試會(huì)更早地掛掉。這是為什么?因?yàn)槲覀冊(cè)谶@組測(cè)試中只有100個(gè)不同的字符串,因此對(duì)它們進(jìn)行駐留就意味著你用到的內(nèi)存就只是存儲(chǔ)這些字符串所需要的空間。而字符串去重的話,會(huì)產(chǎn)生不同的字符串對(duì)象,它僅會(huì)共享底層的char[]數(shù)組。

/**
 * String deduplication vs interning test
 */
public class StringDedupTest {
    private static final int MAX_EXPECTED_ITERS = 300;
    private static final int FULL_ITER_SIZE = 100 * 1000;

    //30M entries = 120M RAM (for 300 iters)
    private static List<String> LIST = new ArrayList<>( MAX_EXPECTED_ITERS * FULL_ITER_SIZE );

    public static void main(String[] args) throws InterruptedException {
        //24+24 bytes per String (24 String shallow, 24 char[])
        //136M left for Strings

        //Unique, dedup
        //136M / 2.9M strings = 48 bytes (exactly String size)

        //Non unique, dedup
        //4.9M Strings, 100 char[]
        //136M / 4.9M strings = 27.75 bytes (close to 24 bytes per String + small overhead

        //Non unique, intern
        //We use 120M (+small overhead for 100 strings) until very late, but can't extend ArrayList 3 times - we don't have 360M

        /*
          Run it with: -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics
          Give as much Xmx as you can on your box. This test will show you how long does it take to
          run a single deduplication and if it is run at all.
          To test when deduplication is run, try changing a parameter of Thread.sleep or comment it out.
          You may want to print garbage collection information using -XX:+PrintGCDetails -XX:+PrintGCTimestamps
        */

        //Xmx256M - 29 iterations
        fillUnique();

        /*
         This couple of tests compare string deduplication (first test) with string interning.
         Both tests should be run with the identical Xmx setting. I have tuned the constants in the program
         for Xmx256M, but any higher value is also good enough.
         The point of this tests is to show that string deduplication still leaves you with distinct String
         objects, each of those requiring 24 bytes. Interning, on the other hand, return you existing String
         objects, so the only memory you spend is for the LIST object.
         */

        //Xmx256M - 49 iterations (100 unique strings)
        //fillNonUnique( false );

        //Xmx256M - 299 iterations (100 unique strings)
        //fillNonUnique( true );
    }

    private static void fillUnique() throws InterruptedException {
        int iters = 0;
        final UniqueStringGenerator gen = new UniqueStringGenerator();
        while ( true )
        {
            for ( int i = 0; i < FULL_ITER_SIZE; ++i )
                LIST.add( gen.nextUnique() );
            Thread.sleep( 300 );
            System.out.println( "Iteration " + (iters++) + " finished" );
        }
    }

    private static void fillNonUnique( final boolean intern ) throws InterruptedException {
        int iters = 0;
        final UniqueStringGenerator gen = new UniqueStringGenerator();
        while ( true )
        {
            for ( int i = 0; i < FULL_ITER_SIZE; ++i )
                LIST.add( intern ? gen.nextNonUnique().intern() : gen.nextNonUnique() );
            Thread.sleep( 300 );
            System.out.println( "Iteration " + (iters++) + " finished" );
        }
    }

    private static class UniqueStringGenerator
    {
        private char upper = 0;
        private char lower = 0;

        public String nextUnique()
        {
            final String res = String.valueOf( upper ) + lower;
            if ( lower < Character.MAX_VALUE )
                lower++;
            else
            {
                upper++;
                lower = 0;
            }
            return res;
        }

        public String nextNonUnique()
        {
            final String res = "a" + lower;
            if ( lower < 100 )
                lower++;
            else
                lower = 0;
            return res;
        }
    }
}

Java 8 update 20中添加了字符串去重的特性。它是G1垃圾回收器的一部分,因此你必須使用G1回收器才能啟用它:-XX:+UseG1GC -XX:+UseStringDeduplication

字符串去重是G1的一個(gè)可選的階段。它取決于當(dāng)前的系統(tǒng)負(fù)載。

字符串去重會(huì)查詢內(nèi)容相同那些字符串,并將它們底層存儲(chǔ)字符的char[]數(shù)組進(jìn)行統(tǒng)一。使用這一特性你不需要寫任何代碼,不過這意味著最后你得到的是不同的字符串對(duì)象,每個(gè)對(duì)象會(huì)占用24個(gè)字節(jié)。有的時(shí)候顯式地調(diào)用String.intern進(jìn)行駐留還是有必要的。

字符串去重不會(huì)對(duì)年輕的字符串進(jìn)行處理。字符串處理的最小年齡是通過-XX:StringDeduplicationAgeThreshold=3的JVM參數(shù)來進(jìn)行管理的(3是這個(gè)參數(shù)的默認(rèn)值)

感謝各位的閱讀,以上就是“如何理解字符串常量池”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)如何理解字符串常量池這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

當(dāng)前題目:如何理解字符串常量池
本文鏈接:http://bm7419.com/article48/igsihp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站制作、微信公眾號(hào)、面包屑導(dǎo)航、靜態(tài)網(wǎng)站、網(wǎng)站內(nèi)鏈、微信小程序

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)