java如果把字符串转成对象_Java中的重复对象:不仅仅是字符串
當Java應用程序消耗大量內存時,它本身就會出現問題,并可能導致GC壓力增加和GC暫停時間過長。在我之前的一篇文章中,我討論了Java中常見的內存浪費源:重復字符串。兩個 java.lang.String 對象, a 并 b 在重復時 a != b && a.equals(b)。換句話說,在JVM存儲器中有兩個(或更多)單獨的字符串具有相同的內容。此問題經常發生,尤其是在業務應用程序中。在這樣的應用程序中,字符串代表了許多真實世界的數據,然而,相應的數據域(例如客戶名稱,國家名稱,產品名稱)是有限的并且通常很小。根據我的經驗,在未經優化的Java應用程序中,重復的字符串通常會浪費5到30%的堆。但是,你有沒有想過其他類的實例,包括數組,有時也會重復,浪費了相當多的內存?如果沒有,請繼續閱讀。
對象復制方案
只要某種類型的不同對象的數量有限,但應用程序不斷創建此類對象而不嘗試緩存/重用現有對象,就會發生內存中的對象復制。以下是我看到的對象復制的幾個具體示例:
- 在Hadoop文件系統(HDFS)NameServer中, byte[] 數組而不是 Strings用于存儲文件名。當在不同目錄中存在具有相同名稱的文件時,相應的 byte[] 陣列是重復的。有關 詳細信息,請參閱此票證。
- 在一些監視系統中,從被監視實體(機器,應用程序,組件等)接收的周期性“事件”或“更新”被表示為具有兩個主要字段的小對象:時間戳和值。當許多更新同時到達并且所有更新都具有相同的值(例如,0或1表示被監視實體的健康狀況良好)時,會創建許多重復對象。
- 該 蜂房數據倉庫曾經有過以下問題。當針對具有大量分區的同一DB表執行多個并發查詢時,每個分區的元數據的單獨的每個查詢副本被加載到內存中。分區元數據表示為 java.util.Properties 實例。因此,針對2000個分區運行50個并發查詢,Properties 用于為這些分區中的每個分區創建50個相同的副本 ,或者總共100,000個這樣的集合,這消耗了大量內存。有關詳細信息,請參閱此票證。
這只是幾個例子。其他不太明顯的包括存儲相同消息的多個相同字節緩沖區,具有表示某些頻繁出現的數據組合的相同內容的多個(通常是小的)對象集,等等。
擺脫重復的對象
如上所述,字符串是一類特別容易重復的對象。很久以前JDK開發人員已經實現了這個問題,并使用String.intern()方法解決了這個問題。上面提到的文章詳細討論了它。簡而言之,此方法使用具有有效弱引用的全局字符串緩存(池)。如果它還沒有在緩存中,它會保存并返回給定的字符串實例,或者返回具有相同值的緩存字符串實例。String.intern() 曾經不是很好的 性能和可擴展性 從JDK 7開始大幅改進。因此,當沒有過度使用時,它可能是許多應用程序的良好解決方案。然而,在討論這篇文章在高度并發或性能關鍵的應用程序中,它可能成為瓶頸,可能需要一種不同的“手動”實習方法。
讓我們考慮其他對象實習。請記住,以下討論僅適用于不可變對象,即在創建后不會更改的對象。如果對象內容可以改變,消除重復變得更加困難,并且需要定制的逐案解決方案。
最廣泛使用的現成實習功能由Guava庫通過com.google.commmon.collect.Interners類提供。此類有兩個返回內部實例的關鍵方法: newStrongInterner() 和 newWeakInterner()。弱內部函數最終會釋放不再需要的對象(未在任何地方強烈引用),通常使用較少的內存,因此更頻繁地使用。它被實現為具有類似于標準JDK的弱鍵的并發哈希集 ConcurrentHashMap。在許多情況下,這是一個很好的選擇,有助于通過較小的CPU性能開銷大幅減少內存占用,這通常會減少GC時間。但是,請考慮以下情況:
- 一些C類有2000萬個實例,每個實例占32個字節
- 其中1000萬個實例彼此完全相同,另外1000萬個實例都是截然不同的。在實踐中,這種尖銳的劃分幾乎從未發生過,但是,大約一半的物體僅表示少數獨特的值,而在另一半中,大多數物體很少或沒有重復,這種情況非常常見。簡化的劃分使我們的計算更容易。
在這種情況下,當我們不實習任何C實例時,它們使用32 * 20M = 640M字節。但是如果我們intern() 為每個人調用番石榴會發生什么 呢?
前1000萬個對象將成功減少到只有一個C實例,占用的內存可以忽略不計。但是,對于剩下的1000萬個對象中的每一個,都沒有節省,因為它們中的每一個都是唯一的。盡管如此,Guava弱內部人員將維持一個內部表格,其中包含1000萬個條目以容納這些對象中的每一個。該表將使用com.google.common.collect.MapMakerInternalMap$WeakKeyDummyValueEntry 每個實習對象的一個類實例 ,以及引用每個條目的內部數組中的一個插槽。a的大小 WeakKeyDummyValueEntry是40個字節,因此每個實習對象需要40 + 4 = 44個字節。44 * 10M = 440M。添加到32 * 10M = 320M,C的唯一實例仍然占用,現在總內存占用量為760M字節!換句話說,我們使用更多的內存比以前,而不是更少。
這種情況可能有點極端,但在實踐中,一般規則仍然存在:如果在給定的一組對象中,唯一對象的百分比很高,那么傳統的內部存儲器可以節省內存,存儲對每個對象的引用給予它,可能很小,如果不是負面的話。我們可以做得更好嗎?
固定大小的陣列,無鎖(FALF)內部
事實證明,如果我們不需要每個唯一對象的單個副本,而只是想節省內存,并且可能仍然有一些重復的對象 - 換句話說,如果我們同意“機會性地”重復刪除對象 - 有一個簡單而有效的解決方案。我沒有在文獻中看到它,所以我把它命名為“固定大小數組,無鎖(FALF)Interner”。
此interner實現為一個小的,固定大小,基于開放哈希映射的對象緩存。當存在高速緩存未命中時,給定插槽中的高速緩存對象始終用新對象替換。沒有鎖定和沒有同步,因此沒有相關的開銷。基本上,這個緩存是基于這樣的想法:具有值X的具有許多副本的對象具有更高的機會保持在緩存中足夠長的時間以保證在錯過驅逐X之前自己的幾個緩存命中并且用具有對象的對象替換它。一個不同的值Y.這是這個interner的一個可能的實現:
/** Fixed size array, lock free object interner */staticclassFALFInterner<T>{staticfinalintMAXIMUM_CAPACITY=1<<30;privateObject[]cache;FALFInterner(intexpectedCapacity) {cache=newObject[tableSizeFor(expectedCapacity)];}Tintern(Tobj) {intslot=hash(obj)&(cache.length-1);TcachedObj=(T)cache[slot];if(cachedObj!=null&&cachedObj.equals(obj))returncachedObj;else{cache[slot]=obj;returnobj;}}/** Copied from java.util.HashMap */staticinthash(Objectkey) {inth;return(key==null)?0: (h=key.hashCode())^(h>>>16);}/*** Returns a power of two size for the given target capacity.* Copied from java.util.HashMap.*/staticinttableSizeFor(intcap) {intn=cap-1;n|=n>>>1;n|=n>>>2;n|=n>>>4;n|=n>>>8;n|=n>>>16;return(n<0)?1: (n>=MAXIMUM_CAPACITY)?MAXIMUM_CAPACITY:n+1;}}為了比較FALF interner和Guava weak interner的性能,我寫了一個簡單的多線程基準測試。代碼生成并實現從遵循高斯分布的隨機數派生的字符串。也就是說,具有某些值的字符串比其他字符串更頻繁地生成,因此將導致更多重復。在這個基準測試中,FALF interner運行速度比Guava interner快約17%。但是,并非全部。當我測量內存占用時 jmap -histo:live 基準測試完成后運行,但在退出之前,事實證明,使用FALF interner,使用的堆大小比Guava interner小近30倍!這是固定大小的小緩存與弱散列映射的內存占用量的差異,其中每個獨特對象都有一個條目。
公平地說,FALF interner通常需要比傳統的,一刀切的內部調整器更多的調整。首先,由于緩存大小是固定的,您需要仔細選擇它。一方面,為了最大限度地減少未命中,這個大小應該足夠大 - 理想情況下等于你實習生類型的唯一對象的數量。另一方面,我們的目標是最小化已用內存,因此在實踐中,您可能會選擇(更大)更小的大小,該大小大致等于具有大量重復項的對象的數量。
另一個重要的考慮因素是為被攔截對象選擇散列函數。在一個小的,固定大小的緩存中,盡可能均勻地在插槽中分布對象非常重要,以避免不經常使用許多插槽時的情況,而有一個小組,其中每個插槽由幾個對象值爭用很多副本。這種爭用將導致緩存遺漏許多重復,因此,更大的內存占用。當這種情況發生在具有非常簡單hashCode() 方法的類的實例時 (例如,代碼類似于java.lang.String 類中的代碼 ),它可能表明該散列函數實現是不合適的。更高級的哈希函數,就像com.google.common.hash.Hashing提供的哈希函數之一 類,可以大大提高FALF interner的效率。
檢測重復對象
到目前為止,我們還沒有討論過開發人員如何確定應用程序中的哪些對象有很多重復項,因此需要進行實習。對于大型應用程序,這可能是非平凡的。即使您可以猜測哪些對象可能重復,也很難估計其確切的內存影響。根據經驗,解決此問題的最佳方法是生成應用程序的堆轉儲,然后使用工具對其進行分析。
堆轉儲本質上是正在運行的JVM堆的完整快照。它可以通過調用jmap 實用程序在任意時刻進行 ,也可以將JVM配置為在失敗時自動生成它 OutOfMemoryError。如果你谷歌“JVM堆轉儲”,你會立即看到一堆關于這個主題的相關文章。
堆轉儲是一個大小與JVM堆大小相同的二進制文件,因此只能使用特殊工具讀取和分析它。有許多這樣的工具,包括開源和商業。最流行的開源工具是Eclipse MAT; 還有VisualVM和一些不那么強大,鮮為人知的工具。商業工具包括通用Java分析器:JProfiler和YourKit,以及JXRay - 專門為堆轉儲分析構建的工具。
與大多數其他工具不同,JXRay會立即分析堆轉儲以解決大量常見問題,包括重復字符串和其他對象。目前,對象比較淺薄。也就是說,只有兩個對象(例如 ArrayLists)x0, x1, x2, ... 以相同的順序引用完全相同的對象組時才被認為是重復的 。換一種說法,兩個對象 a 和 b 被認為是平等的,都指向其他對象 a 和 b 使用位是相等的,有點。
JXRay運行一次給定的堆轉儲,并生成一個包含HTML格式的所有收集信息的報告。這種方法的優點是,您可以隨時隨地查看分析結果,并輕松與他人分享。這也意味著您可以在任何機器上運行該工具,包括數據中心中功能強大但功能強大的“無頭”機器。
從JXRay獲得報告后,在您喜歡的瀏覽器中打開它并展開相關部分。你可能會看到這樣的事情:
因此,在此轉儲中,24.7%的已使用堆被重復的非數組非集合對象浪費!上表列出了其實例對此開銷貢獻最大的所有類。要查看這些對象的來源(哪些對象引用它們,一直到GC根),向下滾動到報告的“昂貴數據字段”或“完整參考鏈”子部分,展開它,然后單擊相關表中的一行。以下是上述類之一的示例 TopicPartition:
從這里,我們可以很好地了解哪些數據結構可以管理有問題的對象。
總而言之,重復對象,即具有相同內容的同一類的多個實例,可能成為Java應用程序的負擔。它們可能會浪費大量內存和/或增加GC壓力。衡量此類對象影響的最佳方法是獲取堆轉儲并使用JXRay之類的工具對其進行分析。當重復對象是不可變的時,您可以使用現成的或自定義的內部實現來減少此類對象的數量,從而減少其內存影響。可變復制對象更難以擺脫,并且可能只能通過逐個定制的解決方案來消除。
總結
以上是生活随笔為你收集整理的java如果把字符串转成对象_Java中的重复对象:不仅仅是字符串的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: google chrome如何保存密码(
- 下一篇: 苹果联系人导入安卓手机(苹果联系人导入安