內(nèi)容簡(jiǎn)介
本文深入分析并驗(yàn)證了不同Java對(duì)象占用內(nèi)存空間大小的情況。對(duì)于不同的jvm實(shí)現(xiàn),Java對(duì)象占用的內(nèi)存空間大小可能不盡相同,本文主要分析HotSpot jvm中的情況,實(shí)驗(yàn)環(huán)境為64位window10系統(tǒng)、JDK1.8。
對(duì)象頭
在64位機(jī)器上,默認(rèn)不開啟指針壓縮(-XX:-UseCompressedOops)的情況下,對(duì)象頭占用16bytes,開啟指針壓縮(-XX:+UseCompressedOops)則占用12bytes。
實(shí)例數(shù)據(jù)
原生類型(primitive type)的內(nèi)存占用如下:

對(duì)象引用(reference)類型在64位機(jī)器上,關(guān)閉指針壓縮時(shí)占用8bytes, 開啟時(shí)占用4bytes。
對(duì)齊填充
Java對(duì)象占用空間是8字節(jié)對(duì)齊的,即所有Java對(duì)象占用bytes數(shù)必須是8的倍數(shù)。包含兩個(gè)屬性的對(duì)象:int和byte,并不是占用17bytes(12+4+1),而是占用24bytes(對(duì)17bytes進(jìn)行8字節(jié)對(duì)齊)
對(duì)象內(nèi)存占用(前提回顧)
首先根據(jù)以上的計(jì)算規(guī)則,進(jìn)行一個(gè)簡(jiǎn)單的驗(yàn)證。使用下面的程序進(jìn)行驗(yàn)證:
public class Test {
public static void main(String[] args) throws InterruptedException {
TestObject testObject = new TestObject();
Thread.sleep(600 * 1000);
System.out.println(testObject);
}
}
class TestObject {
private int i;
private double d;
private char[] c;
public TestObject() {
this.i = 1;
this.d = 1.0;
this.c = new char[]{'a', 'b', 'c'};
}
}
TestObject對(duì)象有四個(gè)屬性,分別為int, double, Byte, char[]類型。在打開指針壓縮(-XX:+UseCompressedOops)的情況下,在64位機(jī)器上,TestObject占用的內(nèi)存大小應(yīng)為:
12(Header) + 4byte(int) + 8byte(double) + 4byte(reference) = 28 (bytes),加上4byte對(duì)齊(padding),最終的大小應(yīng)為32bytes。

當(dāng)指針壓縮關(guān)閉時(shí)(-XX:-UseCompressedOops),在64位機(jī)器上,TestObject占用的內(nèi)存大小應(yīng)為:
16(Header) + 4(int) + 8(double) + 8(reference) = 36 (bytes),4字節(jié)對(duì)齊后為 40 bytes。

包裝類型
包裝類(Boolean/Byte/Short/Character/Integer/Long/Double/Float)占用內(nèi)存的大小等于對(duì)象頭大小加上底層基礎(chǔ)數(shù)據(jù)類型的大小。
包裝類型的對(duì)象內(nèi)存占用情況如下:

數(shù)組
64位機(jī)器上,數(shù)組對(duì)象的對(duì)象頭占用24 bytes,啟用壓縮后占用16字節(jié)。比普通對(duì)象占用內(nèi)存多是因?yàn)樾枰~外的空間存儲(chǔ)數(shù)組的長(zhǎng)度。
基礎(chǔ)數(shù)據(jù)類型數(shù)組占用的空間包括數(shù)組對(duì)象頭以及基礎(chǔ)數(shù)據(jù)類型數(shù)據(jù)占用的內(nèi)存空間。
對(duì)象數(shù)組中存放的是對(duì)象的引用,所以對(duì)象數(shù)組本身的大小=數(shù)組對(duì)象頭+length * 引用指針大小,總大小為對(duì)象數(shù)組本身大小+存放的數(shù)據(jù)的大小之和。
舉兩個(gè)例子:
int[10]:
開啟壓縮:16(12Byte(8[標(biāo)記字段]+4[類型指針])+4[數(shù)組長(zhǎng)度大小]) + 10(大小) * 4(32bit的整數(shù)類型) = 56 bytes;

關(guān)閉壓縮:24(16Byte(8[標(biāo)記字段]+8[類型指針])+4[數(shù)組長(zhǎng)度大小]+4[padding]) + 10 * 4(32bit的整數(shù)類型) = 64bytes

new Integer[3]:
關(guān)閉壓縮:
Integer數(shù)組本身:24(header) + 3 * 8(Integer reference) = 48 bytes;
總共:48 + 3 * 24(Integer) = 120 bytes。

開啟壓縮:
Integer數(shù)組本身:16(header) + 3 * 4(Integer reference) = 28+4(padding) -> 32 (bytes)
總共:32 + 3 * 16(Integer)[12byte的對(duì)象頭+4byte的引用reference] = 80 (bytes)

String
在JDK1.7及以上版本中,String包含2個(gè)屬性,一個(gè)用于存放字符串?dāng)?shù)據(jù)的char[], 一個(gè)int類型的hashcode, 部分源代碼如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
}
因此,在關(guān)閉指針壓縮時(shí),一個(gè)String本身需要 16(Header) + 8(char[] reference) + 4(int) = 32 bytes。
除此之外,一個(gè)char[]占用24byte(其中有4byte的對(duì)齊padding) + length * 2 bytes(8字節(jié)對(duì)齊), 即一個(gè)String占用的內(nèi)存空間大小為:
56 + length * 2 bytes(char的18bit) (8字節(jié)對(duì)齊)。
舉幾個(gè)例子。
一個(gè)空字符串("")的大小應(yīng)為:56 + 0 * 2 bytes = 56 bytes。

字符串"abc"的大小應(yīng)為:56 + 3 * 2 = 62(8字節(jié)對(duì)齊)->64 (bytes)

字符串"abcde"的大小應(yīng)為:56 + 5 * 2 = 66->72 (bytes)

字符串"abcde"在開啟指針壓縮時(shí)的大小為:
String本身:12(Header) + 4(char[] reference) + 4(int hash) = 20(padding) -> 24 (bytes);
存儲(chǔ)數(shù)據(jù):16(char[] header) + 5 * 2 = 26(padding) -> 32 (bytes)
總共:24 + 32 = 56 (bytes)
