從 Java 代碼到 Java 堆
理解和優化您的應用程序的內存使用
https://www.ibm.com/developerworks/cn/java/j-codetoheap/
優化應用程序代碼的內存使用並不是一個新主題,但是人們通常並沒有很好地理解這個主題。本文將簡要介紹 Java 進程的內存使用,隨後深入探討您編寫的 Java 代碼的內存使用。最後,本文將展示提高代碼內存效率的方法,特別強調了 HashMap
和 ArrayList
等 Java 集合的使用。
通過在命令行中執行 java
或者啓動某種基於 Java 的中間件來運行 Java 應用程序時,Java
運行時會創建一個操作系統進程,就像您運行基於 C 的程序時那樣。實際上,大多數 JVM 都是用 C 或者 C++
語言編寫的。作爲操作系統進程,Java 運行時面臨着與其他進程完全相同的內存限制:架構提供的尋址能力以及操作系統提供的用戶空間。
架構提供的內存尋址能力依賴於處理器的位數,舉例來說,32 位或者 64 位,對於大型機來說,還有 31 位。進程能夠處理的位數決定了處理器能尋址的內存範圍:32 位提供了 2^32 的可尋址範圍,也就是 4,294,967,296 位,或者說 4GB。而 64 位處理器的可尋址範圍明顯增大:2^64,也就是 18,446,744,073,709,551,616,或者說 16 exabyte(百億億字節)。
處理器架構提供的部分可尋址範圍由 OS 本身佔用,提供給操作系統內核以及 C 運行時(對於使用 C 或者 C++ 編寫的 JVM 而言)。OS 和 C 運行時佔用的內存數量取決於所用的 OS,但通常數量較大:Windows 默認佔用的內存是 2GB。剩餘的可尋址空間(用術語來表示就是用戶空間 )就是可供運行的實際進程使用的內存。
對於 Java 應用程序,用戶空間是 Java 進程佔用的內存,實際上包含兩個池:Java 堆和本機
(非 Java)堆。Java 堆的大小由 JVM 的 Java 堆設置控制:-Xms
和 -Xmx
分別設置最小和最大 Java 堆。在按照最大的大小設置分配了 Java 堆之後,剩下的用戶空間就是本機堆。圖 1 展示了一個 32 位 Java 進程的內存佈局:
圖 1. 一個 32 位 Java 進程的內存佈局示例
在 圖 1 中,可尋址範圍總共有 4GB,OS 和 C 運行時大約佔用了其中的 1GB,Java 堆佔用了將近 2GB,本機堆佔用了其他部分。請注意,JVM 本身也要佔用內存,就像 OS 內核和 C 運行時一樣,而 JVM 佔用的內存是本機堆的子集。
在您的 Java 代碼使用 new
操作符創建一個 Java 對象的實例時,實際上分配的數據要比您想的多得多。例如,一個 int
值與一個 Integer
對象(能包含 int
值的最小對象)的大小比率是 1:4,這個比率可能會讓您感到吃驚。額外的開銷源於 JVM 用於描述 Java 對象的元數據,在本例中也就是 Integer
。
根據 JVM 的版本和供應的不同,對象元數據的數量也各有不同,但其中通常包括:
-
類
:一個指向類信息的指針,描述了對象類型。舉例來說,對於
java.lang.Integer
對象,這是java.lang.Integer
類的一個指針。 - 標記 :一組標記,描述了對象的狀態,包括對象的散列碼(如果有),以及對象的形狀 (也就是說,對象是否是數組)。
- 鎖 :對象的同步信息,也就是說,對象目前是否正在同步。
對象元數據後緊跟着對象數據本身,包括對象實例中存儲的字段。對於 java.lang.Integer
對象,這就是一個 int
。
如果您正在運行一個 32 位 JVM,那麼在創建 java.lang.Integer
對象實例時,對象的佈局可能如圖 2 所示:
圖 2. 一個 32 位 Java 進程的
java.lang.Integer
對象的佈局示例
如 圖 2
所示,有 128 位的數據被佔用,其中用於存儲 int
值的爲 32 位,而對象元數據佔用了其餘的 96 位。