您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何理解字符串常量池”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何理解字符串常量池”吧!
從平均情況來看,應用程序中的String對象會消耗大量的內存。這里面有一部分是冗余的——同樣的字符串會存在多個不同的實例(a != b, 但a.equals(b))。在實踐中,有許多字符串會出于不同的原因造成冗余。
最初JDK提供了一個String.intern() 方法來解決字符串冗余的問題。這個方法的缺點在于你必須得去找出哪些字符串需要進行駐留(interned)。通常都需要具備冗余字符串查找功能的堆分析的工具才行,比如Youkit profiler。如果使用得當的話,字符串駐留會是一個非常有效的節省內存的工具——它讓你可以重用整個字符串對象(每個字符串對象在底層char[]的基礎上會增加24字節的額外開銷)。
Java 7 update 6開始,每個String對象都有一個自己專屬的私有char[] 。這樣JVM才可以自動進行優化——既然底層的char[]沒有暴露給外部的客戶端的話,那么JVM就能去判斷兩個字符串的內容是否是一致的,進而將一個字符串底層的char[]替換成另一個字符串的底層char[]數組。
字符串去重這個特性就是用來做這個的,它在Java 8 update 20中被引入。下面是它的工作原理:
1.你得使用G1垃圾回收器并啟用這一特性: -XX:+UseG1GC -XX:+UseStringDeduplication
這一特性作為G1垃圾回收器的一個可選的步驟來實現的,如果你用的是別的回收器是無法使用這一特性的。
2.這個特性會在G1回收器的minor GC階段中執行。根據我的觀察來看,它是否會執行取決于有多少空閑的CPU周期。因此,你不要指望它會在一個處理本地數據的數據分析器中會被執行,也就是說,WEB服務器中倒是很可能會執行這個優化。
3.字符串去重會去查找那些未被處理的字符串,計算它們的hash值(如果它沒在應用的代碼中被計算過的話),然后再看是否有別的字符串的hash值和底層的char[]都是一樣的。如果找到的話——它會用一個新字符串的char[]來替換掉現有的這個char[]。
4.字符串去重只會去處理那些歷經數次GC仍然存活的那些字符串。這樣能確保大多數的那些短生命周期的字符串不會被處理。字符串的這個最小的存活年齡可以通過 -XX:StringDeduplicationAgeThreshold=3 的JVM參數來指定(3是這個參數的默認值)。
下面是這個實現的一些重要的結論:
沒錯,如果你想享受字符串去重特性的這份免費午餐的話,你得使用G1回收器。
使用parellel GC的話是無法使用它的,而對那些對吞吐量要求比延遲時期高的應用而言,parellel GC應該是個更好的選擇。
字符串去重是無法在一個已加載完的系統中運行的。要想知道它是否被執行了,可以通過 -XX:+PrintStringDeduplicationStatistics參數來運行JVM,并查看控制臺的輸出。
如果你希望節省內存的話,你可以在應用程序中將字符串進行駐留(interned)——那么放手去做吧,不要依賴于字符串去重的功能。
你需要時刻注意的是字符串去重是要處理你所有的字符串的(至少是大部分吧)——也就是說盡管你知道某個指定的字符串的內容是唯一的(比如說GUID),但JVM并不知道這些,它還是會嘗試將這個字符串和其它的字符串進行匹配。這樣的結果就是,字符串去重所產生的CPU開銷既取決于堆中字符串的數量(將新的字符串和別的字符串進行比較),也取決于你在字符串去重的間隔中所創建的字符串的數量(這些字符串會和堆中的字符串進行比較)。
在一個擁有好幾個G的堆的JVM上,可以通過- XX:+PrintStringDeduplicationStatistics選項來看下這個特性所產生的影響究竟有多大。
另一方面,它基本是以一種非阻塞的方式來完成的,如果你的服務器有足夠多的空閑CPU的話,那為什么不用呢?
最后,請記住,String.intern可以讓你只針對你的應用程序中指定的某一部分已知會產生冗余的字符串。通常來說,它只需要比較一個較小的駐留字符串的池就可以了,也就是說你可以更高效地使用你的CPU。不僅如此,你還可以將整個字符串對象進行駐留,這樣每個字符串你還多節省了24個字節。
這里是我用來試驗這一特性的一個測試類。這三個測試都會一直運行到JVM拋出OOM為止,因此你得分別去單獨地運行它們。
第一個測試會創建內容一樣的字符串,如果你想知道當堆中字符串很多的時候,字符串去重會花掉多少時間的話,這個測試就變得非常有用了。盡量給第一個測試分配盡可能多的內存——它創建的字符串越多,優化的效果就越好。
第二三個測試會比較去重(第二個測試)及駐留(interning, 第三個測試)間的差別。你得用一個相同的Xmx設置來運行它們。在程序中我把這個常量設置成了Xmx256M,但是當然了,你可以分配得多點。然而,你會發現,和interning測試相比,去重測試會更早地掛掉。這是為什么?因為我們在這組測試中只有100個不同的字符串,因此對它們進行駐留就意味著你用到的內存就只是存儲這些字符串所需要的空間。而字符串去重的話,會產生不同的字符串對象,它僅會共享底層的char[]數組。
/** * 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的一個可選的階段。它取決于當前的系統負載。
字符串去重會查詢內容相同那些字符串,并將它們底層存儲字符的char[]數組進行統一。使用這一特性你不需要寫任何代碼,不過這意味著最后你得到的是不同的字符串對象,每個對象會占用24個字節。有的時候顯式地調用String.intern進行駐留還是有必要的。
字符串去重不會對年輕的字符串進行處理。字符串處理的最小年齡是通過-XX:StringDeduplicationAgeThreshold=3的JVM參數來進行管理的(3是這個參數的默認值)
感謝各位的閱讀,以上就是“如何理解字符串常量池”的內容了,經過本文的學習后,相信大家對如何理解字符串常量池這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。