[導讀] Java 6,7,8 中的String intern – 字串池這篇文章將要討論Java 6 中是如何實作String intern 方法的,以及這個方法在Java 7 以及Java 8 中做了哪些調整。字串池字串池(有名字串標準化)
在 Java 6 這個參數中沒有太多幫助,因為你仍被限制在固定的 PermGen 記憶體大小中。後續的討論將直接忽略Java 6
你必須設定一個更大的-XX:StringTalbeSize
值(相比較預設的1009 ),如果你希望更多的使用String.intern() — 否則這個方法很快就會遞減到0 (池大小)。
Java 6,7,8 中的String.intern – 字串池
#這篇文章將要討論Java 6 中是如何實作String.intern
方法的,以及這個方法在Java 7 以及Java 8 中做了哪些調整。
字串池
字串池(有名字串標準化)是透過使用唯一的共享String
物件來使用相同的值不同的地址表示字串的過程。你可以使用自己定義的<a href="http://www.php.cn/code/8210.html" target="_blank">Map</a><string string></string>
(根據需要使用weak 引用或soft 引用)並使用map 中的值作為標準值來實現這個目標,或者你也可以使用JDK 提供的String.intern()
。
很多標準禁止在Java 6 中使用String.intern()
因為如果經常使用池會市區控制,有很大的幾率觸發OutOfMemory<a href="http://www.php.cn/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
每個元素包含具有相同hash值的字串列表。一些實作的細節可以從 Java bug 報表中取得 bugs.sun.com/view_bug.do?bug_id=6962930
預設的池大小是 1009 (出現在上面提及的 bug 報告的源碼中,在 Java7u40 中增加了)。在 JAVA 6 早期版本中是一個常數,在隨後的 java6u30 至 java6u41 中調整為可配置的。而在java 7中一開始就是可以配置的(至少在java7u02中是可以配置的)。你需要指定參數 -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 )保留非常短的字串,在使用<code>-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
參數來執行堆的最大值。
这里有两个测试: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中文網其他相關文章!

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于结构化数据处理开源库SPL的相关问题,下面就一起来看一下java下理想的结构化数据处理类库,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于PriorityQueue优先级队列的相关知识,Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于java锁的相关问题,包括了独占锁、悲观锁、乐观锁、共享锁等等内容,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于多线程的相关问题,包括了线程安装、线程加锁与线程不安全的原因、线程安全的标准类等等内容,希望对大家有帮助。

本篇文章给大家带来了关于Java的相关知识,其中主要介绍了关于关键字中this和super的相关问题,以及他们的一些区别,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于枚举的相关问题,包括了枚举的基本操作、集合类对枚举的支持等等内容,下面一起来看一下,希望对大家有帮助。

封装是一种信息隐藏技术,是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法;封装可以被认为是一个保护屏障,防止指定类的代码和数据被外部类定义的代码随机访问。封装可以通过关键字private,protected和public实现。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于设计模式的相关问题,主要将装饰器模式的相关内容,指在不改变现有对象结构的情况下,动态地给该对象增加一些职责的模式,希望对大家有帮助。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)