陷阱 - 實用的字串,以便你可以使用是一個壞主意

當一些程式設計師看到這個建議:

“使用 == 測試字串是不正確的(除非字串被中斷)”

他們最初的反應是實習字串,以便他們可以使用 ==。 (畢竟 == 比呼叫 String.equals(...) 更快,不是嗎。)

從許多方面來看,這是錯誤的方法:

脆弱性

首先,如果你知道你正在測試的所有 String 物件都被實習,你只能安全地使用 == 。JLS 保證原始碼中的字串文字將被實現。但是,除了 String.intern(String) 本身之外,沒有一個標準的 Java SE API 保證返回實習字串。如果你錯過了一個未被實習的 String 物件來源,你的應用程式將不可靠。這種不可靠性將表現為假陰性,而不是容易使其更難檢測的異常。

使用’intern()‘的成本

在引擎蓋下,實習通過維護包含先前實習的 String 物件的雜湊表來工作。使用某種弱引用機制使得實習雜湊表不會成為儲存洩漏。雖然雜湊表是用本機程式碼實現的(與 HashMapHashTable 等不同),但就所使用的 CPU 和記憶體而言,intern 呼叫仍然相對昂貴。

這個成本必須與我們將通過使用 == 而不是 equals 獲得的節省進行比較。實際上,除非將每個實習字串與其他字串進行幾次比較,否則我們不會收支平衡。

(旁白:少數實習的情況往往是減少應用程式的記憶體佔用,其中相同的字串重複多次,並且這些字串具有較長的生命週期。)

對垃圾收集的影響

除了上面描述的直接 CPU 和記憶體成本之外,實際的字串也會影響垃圾收集器的效能。

對於 Java 7 之前的 Java 版本,實習字串儲存在不經常收集的 PermGen 空間中。如果需要收集 PermGen,則(通常)會觸發完整的垃圾回收。如果 PermGen 空間完全填滿,即使常規堆空間中有可用空間,JVM 也會崩潰。

在 Java 7 中,字串池從 PermGen 移出到普通堆中。但是,雜湊表仍然是一個長期存在的資料結構,這將導致任何實習字串長壽。 (即使實際的字串物件是在 Eden 空間中分配的,它們很可能在收集之前被提升。)

因此,在所有情況下,實習字串將延長其相對於普通字串的生命週期。這將增加 JVM 生命週期內的垃圾收集開銷。

第二個問題是雜湊表需要使用某種弱引用機制來防止字串內部洩漏記憶體。但是這樣的機制對垃圾收集器來說更有用。

這些垃圾收集開銷難以量化,但毫無疑問它們確實存在。如果你廣泛使用 intern,它們可能很重要。

字串池雜湊表大小

根據這個來源 ,從 Java 6 開始,字串池被實現為具有鏈的固定大小的雜湊表,以處理雜湊到同一桶的字串。在 Java 6 的早期版本中,雜湊表具有(硬連線)常量大小。調整引數(-XX:StringTableSize)被新增為 Java 6 的中期更新。然後,在 Java 7 的中期更新中,池的預設大小從 1009 更改為 60013

最重要的是,如果你打算在程式碼中密集使用 intern建議選擇雜湊表大小可調的 Java 版本,並確保適當調整大小。否則,隨著池變大,intern 的效能可能會降低。

作為潛在的拒絕服務向量實習

字串的雜湊碼演算法是眾所周知的。如果你是惡意使用者或應用程式提供的實習字串,則可以將其用作拒絕服務(DoS)攻擊的一部分。如果惡意代理安排它提供的所有字串都具有相同的雜湊碼,這可能導致不平衡的雜湊表和 O(N) 效能為 intern …其中 N 是衝突字串的數量。

(對服務發起 DoS 攻擊有更簡單/更有效的方法。但是,如果 DoS 攻擊的目標是破壞安全性或轉義一線 DoS 防禦,則可以使用此向量。)