0、說明
根據算法書上的定義,一個算法的空間復雜度包括算法程序所占用的空間,輸入初始數據所占用的空間以及算法執行過程中所需要的額外空間。
本文各種結論全部參考過標准文獻,本人也進行過驗證。驗證過程本文不做說明。例如:當前主流虛擬機boolean類型運行時確實是1字節。
部分與計算空間無關的細節也不做說明,例如:對象頭具體包含哪些信息、分別在哪幾位、什么是指針壓縮等。
細節信息,本人以后會在《JVM淺析》欄目中一一補充,敬請期待哦~
一、基礎知識
1. 數據類型以及大小
基本類型 | |
類型名稱 | 占用字節數 |
boolean | 1 |
byte | 1 |
char | 2 |
short | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
引用類型 | |
操作系統位數 | 占用字節數 |
32位 | 4 |
64位 | 8 (指針壓縮后4字節) |
*注:引用類型(注意是引用類型變量,不是對象實例,本質上是指針,其中數組類型變量也屬於引用類型變量)。
2. 內存計算公式
對象占用內存 = 對象頭開銷 + 實例數據(如果是引用類型則包括 變量 和 實例 兩部分開銷) + 填充數據。
a.對象頭開銷
指向類引用、垃圾收集信息、同步信息等。
32位JVM 對象頭8字節,數組對象頭16字節。
64位JVM 對象頭16字節(指針壓縮后12字節),數組對象頭24字節(指針壓縮后16字節)。
空對象 / 空數組 都只有對象頭。
b.實例數據
基本類型在內存只有值所占用空間大小。
引用類型包含 變量 和 值 兩部分占用空間大小(引用變量就相當於指針,用一個系統存儲單元存儲。值則是堆中實例的大小)。
c.填充數據
hotspot JVM里,對象占用空間是8字節對齊的。因為在內存中一個存儲單元是8字節的。
意思是一個Java對象使用的內存一定是8字節的整數倍,如果通過計算后發現對象所需內存不是8字節的整數倍,則會將其填充為8字節的倍數。
例如:對象實例可能需要內存為30字節或者28字節等,都會被填充為32字節。
d.繼承關系(不計算父類對象頭開銷)
子類對象占用內存 = 子類對象頭開銷 + 子類實例數據 + (父類實例數據+填充數據) + 填充數據。
二、計算實例
因為現在主流都是64位系統,下面測試類會按照64位環境計算內存。並且沒有考慮指針壓縮的情況。
測試類演示了最復雜的情況,只為演示計算過程,命名不規范,請勿在實際項目中模仿。
new B()總共占用內存空間 = [ (4(a) + 2(bs) + 2(填充數據)) + 4(ba) + 8(cs數組引用變量) + 4(填充數據) ] + [ 24(cs數組對象頭) + 3(cs數組內引用變量個數,即cs[0]、cs[1]、cs[2]) * 8(cs數組中引用變量占用內存) ] + [ (16(C實例字節頭信息) + 1(c變量) + 7(填充字節)) * 3(C實例數量) ] = 144字節(8字節的整數倍)。
備注
64位JVM才會有指針壓縮的情況。-XX:+UseCompressedOops(開啟) -XX:-UseCompressedOops(關閉)。
JDK1.6以后才加入此項功能並且默認開啟指針壓縮。但計算公式不變,只是有些對象占用內存數值會變。大家自行計算壓縮指針后或者32位JVM下的占用內存。
查看指針壓縮等虛擬機參數信息可用如下兩個命令:
jcmd //查看進程信息 (數字列顯示的是PID,要保證程序在運行過程中,想測試的話可以到公司服務器上查看)。
jcmd [PID] VM.flags //查看該pid對應進程設置的JVM參數。