本文將介紹接下來的技巧和主題:
- 在包裝器上使用基元字段(例如,布爾值 ->布爾值)
- 減少形成平面結構的類的數量(在一個或多個類中折疊類)
- 盡可能使用窄數據類型(例如,代替,代替等)short``int``long``Date
- 使用掩碼將一種類型隱藏在另一種類型中(例如,短內有許多布爾值)
讓我們切入正題,回顧一下我們將在示例中使用的類。我們將改變其結構并逐步估計其大小四次。
計算第一個快照大小
如上所述,我建議查看之前的文章以了解內存計算過程。在我們的例子中,我們將使用基于 64 位 JAVA 的計算。我們將使用Alexey Shipilev開發的JOL核心庫驗證所有計算。您可以在本手冊中找到此庫的示例。
啟動用戶對象
首先,讓我們創建 3 個對象并設置所有字段。在我們的示例中,我們將使用所有唯一對象,甚至對于布爾值,我們也將使用新實例(通過使用)。在這種情況下,我們的計算將是最悲觀的(從大小的角度來看),但完全正確:new boolean
現在讓我們計算每個對象的大小(再次考慮到甚至所有布爾實例都是唯一的):
所以畢竟,總大小的實例是 320 字節(包括和實例一起)。為了驗證它,讓我們使用 JOL 核心庫并打印它們的大小:User``UserSalary``UserPayment
打印結果為 56 字節、136 字節和 320 字節。
現在讓我們改進這個荒謬的例子并正確啟動布爾值。
在前面的示例中,我將布爾值作為唯一對象啟動。在這里,我們將以重用sameandreferences的方式啟動(這也是你經常初始化它們的方式)。通過這樣的初始化,我們的例子將更加現實。(此步驟不是優化,它只是解釋內存計算如何工作的附加步驟。Boolean.true``Boolean.false
現在我們必須重新計算所有對象,因為我們基本上改變了布爾初始化:
打印結果符合預期:56 字節、136 字節和 208 字節(從 320 字節減少)。
第一個內存優化:用基元替換所有包裝器
在這里,我們通過僅使用原語而不是包裝器對象來進行第一次真正的優化。在這種情況下,我們將失去空收益選項,默認情況下將初始化所有值。
現在,讓我們重新計算快照大小,考慮到所有基元值都沒有額外的引用并保留在其容器對象中:
如您所見,所有包裝器對象都會添加 16 個額外的字節。這種轉換將總大小從 208 字節減少到 112 字節,幾乎減少了 2 倍。
第二次內存優化:在一個類中折疊數據
在大多數情況下,這種優化是不可能的,或者至少使 OOP 結構的可讀性和可維護性降低;但是在某些內存不足的緊急情況下,您別無選擇。因此,讓我們將所有字段移動到一個類上,并查看改進:
現在只有一個用戶對象,我們也失去了在引用上花費的額外內存。是的,代碼更具可讀性和可維護性,但我們幾乎將大小提高了 2 倍。JOL-Core 庫還確認現在的總大小為 64 字節!
第三個內存優化:使用窄類型
如果您檢查我們使用的所有類型,您可能會提到其中一些涵蓋的范圍比我們需要的要大:例如,isvalue 和覆蓋范圍從 -2147483648 到 -2,147,483,647,但根據我們的業務需求,它不會超過每月 32,767 美元。因此,我們可以使用數據類型而不是整數。同樣,我們可以替換**java.sql.Date并只保留長**值(已經是未來和過去的數千年)。salary``int``Short
因此,在我們的例子中,我們將進行以下更改:
現在,快照總大小為 40 字節大小。我們能進一步改進它嗎?是的!
第四個內存優化:使用窄類型
在此步驟中,我們將在較大的類型中“隱藏”較小的數據類型。整數值由 2^32 個值覆蓋。布爾值由 2^1 覆蓋。所以在一個整數中,我們可以“隱藏”32個布爾值。同樣的事情可以應用于這些示例:
- 1 個整數 (2^32) 內的 32 個布爾值 (2^1)
- 1 個整數 (2^32) 內的 4 個字節 (2^7)
- 1 個整數 (2^32) 內的 2 個短褲 (2^16)
- 2 個整數 (2^32) 在一個長 (2^64) 內
- 等。
在我們的示例中,我們將所有 9 個布爾值封裝在一個短時間內。我們將使用按位運算(向右、向左移動等)。您可以在本文中找到更多示例。
從布爾值到短線的轉換:
轉換包括 3 個后續步驟:boolean``short
- 轉換。boolean``1 (true)``0 (false)
- 使用左移運算符將該值向左移動。N``(<<)
- 使用運算符合并所有值。OR (|)
因此,使用定義的標志順序如下:
并在一種方法中實現所有步驟:
現在有了這個函數,我們可以在一個單一中隱藏值:boolean``short
從短值中揭示隱藏的布爾值
為了識別哪個標志具有什么值,我們需要進行向后轉換:
- 將值移動到右邊的步驟(取決于標志順序)。results``N
- 進行比較以“削減”正確的數字。
- 將此數字與 1 進行比較,如果為 1,則值為 true。1``=> flags
所有步驟如下所示:
現在使用所有這些函數,我們可以嘗試隱藏下一個值:
我們的最終值是229,或者可以用二進制格式表示為011100101并表示下一個值,如下所示:
現在有了這個結果值,我們可以使用并得到一個特定的封裝布爾值:mask
最終的計算是 我們類的總大小為 32 字節。User
結論
在我們的示例中進行了四次轉換之后,我們得到了下一個足跡改進:
完成所有轉換后,我們將類的大小從 208 字節減少到 32 字節,幾乎減少了7 倍。我們的對象更難讀取和維護,但內存消耗急劇下降。在節省 1000 萬用戶的情況下,我們只需要 320MB 而不是 2.1GB。