的實際內存開銷對象實例隱含依賴於JVM實現內部的一些細節,並且可能難以定義,因爲它可以在整個對象的生命週期變化(在垃圾收集器中,對象可以在使用不同內存管理結構的世代之間「移動」)。
一個非常粗略的近似值是,任何對象的每個實例都包含兩個「字」(32位機器上的兩個32位值,64位機器上的兩個64位值)。其中的一個或多或少是指向該對象的實例的指針,另一個則保存某個對象狀態,例如該對象的監視器(您使用鎖定的那個對象)。然後是對象字段。對於一個數組,數組的長度必須寫在對象的某個地方,以及值。
此時,請查看Java類的源代碼(查找JDK發行版中名爲src.zip
的文件)。在String.java
文件中,我們可以看到,在內部,String
實例有四個字段:對值爲char
的數組的引用,以及三個int
(其中一個是數組中第一個字符串的索引,第二個是字符串長度,第三個緩存字符串散列碼)。因此,對於一個32位機,可以估算出,的Ñ字符String
實例的最小的內存使用情況是的總和:
- 爲
String
實例對象頭兩個32位字
- 四爲
String
實例字段的32位字
- 用於陣列實例頭和長度3個32位字
- ñ爲字符本身16位字(一個
char
是16位)
這只是一個最低限度,因爲String
實例只引用內部字符數組的塊,所以數組的內存大小可以更大。另一方面,字符陣列可以在多個String
實例之間共享。這種結構允許String.substring()
非常快:新String
實例內部使用相同的陣列,所以沒有涉及數據複製;但這也意味着如果你有一個很大的字符串,拿一個小的字符串,並且存儲那小的子字符串,你實際上也保留在RAM中的大陣列(對於String
實例str
,你可以使new String(str)
得到一個新的實例,它將在內部使用一個新分配和修剪過的數組實例)。好的一面,如果你有兩個字符串,一個是另一個的子字符串,並且你將兩個字符串存儲在緩存中,那麼你只需爲公共內部數組支付一次。
因此,即使不考慮GC暗示的所有隱藏成本,也很難知道「字符串的內存大小」是什麼意思:如果兩個String
實例共享相同的內部數組,那麼如何計算「大小「的每個字符串?
縱觀源HashMap
會告訴你,有這也是分配的內部情況;每個存儲值有一個HashMap.Entry
實例的數組和一個HashMap.Entry
實例。陣列大小根據條目數量和配置的負載因數進行動態調整。
由於佔內存大小是很難的,一個完全不同的解決方法是讓當舊的緩存項應該被刪除的GC自行決定。這在內部使用「軟引用」:它們是某些類型的指針,當內存變得緊張時,GC可能將其設置爲null
(破壞引用可能允許GC釋放更多對象)。這使得粗略的「內存感知」緩存根據可用的RAM自動修剪。一個有用的圖書館是谷歌的番石榴和它的MapMaker類。
(至少在Sun JVM)字符串是內部持有作爲char []而不是[]一個字節,所以一個字符串的堆的大小近似爲2 * str.length()加一個位用於其他領域和對象開銷。 –