摘要:對(duì)于不同的實(shí)現(xiàn),對(duì)象占用的內(nèi)存空間大小可能不盡相同,本文主要分析中的情況,實(shí)驗(yàn)環(huán)境為位系統(tǒng),使用進(jìn)行結(jié)論驗(yàn)證。內(nèi)存占用這里分析一個(gè)只有一組鍵值對(duì)的結(jié)構(gòu)如下首先分析本身的大小。
本文深入分析并驗(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,使用JProfiler進(jìn)行結(jié)論驗(yàn)證。
Java對(duì)象內(nèi)存布局Java對(duì)象的內(nèi)存布局包括:對(duì)象頭(Header),實(shí)例數(shù)據(jù)(Instance Data)和補(bǔ)齊填充(Padding)。
對(duì)象頭:關(guān)于對(duì)象頭的詳細(xì)介紹可以參看我的博文:http://blog.csdn.net/codersha...,這里只關(guān)注其內(nèi)存占用大小。在64位機(jī)器上,默認(rèn)不開啟指針壓縮(-XX:-UseCompressedOops)的情況下,對(duì)象頭占用12bytes,開啟指針壓縮(-XX:+UseCompressedOops)則占用16bytes。
實(shí)例數(shù)據(jù):原生類型(primitive type)的內(nèi)存占用如下:
Primitive Type | Memory Required(bytes) |
---|---|
byte, boolean | 1 byte |
short, char | 2 bytes |
int, float | 4 bytes |
long, double | 8 bytes |
對(duì)象引用(reference)類型在64位機(jī)器上,關(guān)閉指針壓縮時(shí)占用8bytes, 開啟時(shí)占用4bytes。
對(duì)齊填充:Java對(duì)象占用空間是8字節(jié)對(duì)齊的,即所有Java對(duì)象占用bytes數(shù)必須是8的倍數(shù)。例如,一個(gè)包含兩個(gè)屬性的對(duì)象:int和byte,并不是占用17bytes(12+4+1),而是占用24bytes(對(duì)17bytes進(jìn)行8字節(jié)對(duì)齊)
Java對(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) + 4(int) + 8(double) + 4(reference) = 28 (bytes),加上8字節(jié)對(duì)齊,最終的大小應(yīng)為32 bytes。
JProfiler中的結(jié)果為:
可以看到,TestObject占用的內(nèi)存空間大小(Shallow Size)為32 bytes。
關(guān)于Retained Size和Shallow Size的區(qū)別,可以參看:
http://blog.csdn.net/e5945/ar...
當(dāng)指針壓縮關(guān)閉時(shí)(-XX:-UseCompressedOops),在64位機(jī)器上,TestObject占用的內(nèi)存大小應(yīng)為:
16(Header) + 4(int) + 8(double) + 8(reference) = 36 (bytes), 8字節(jié)對(duì)齊后為 40 bytes。
JProfile的結(jié)果為:
包裝類型包裝類(Boolean/Byte/Short/Character/Integer/Long/Double/Float)占用內(nèi)存的大小等于對(duì)象頭大小加上底層基礎(chǔ)數(shù)據(jù)類型的大小。
包裝類型的對(duì)象內(nèi)存占用情況如下:
Numberic Wrappers | +useCompressedOops | -useCompressedOops |
---|---|---|
Byte, Boolean | 16 bytes | 24 bytes |
Short, Character | 16 bytes | 24 bytes |
Integer, Float | 16 bytes | 24 bytes |
Long, Double | 24 bytes | 24 bytes |
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 + 10 * 4 = 56 bytes;
關(guān)閉壓縮:24 + 10 * 4 = 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(padding) -> 32 (bytes) 總共:32 + 3 * 16(Integer) = 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, 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[]占用24 + length * 2 bytes(8字節(jié)對(duì)齊), 即一個(gè)String占用的內(nèi)存空間大小為:
56 + length * 2 bytes (8字節(jié)對(duì)齊)。
舉幾個(gè)例子。
一個(gè)空字符串("")的大小應(yīng)為:56 + 0 * 2 bytes = 56 bytes。
JProfiler結(jié)果:
字符串"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)常用數(shù)據(jù)結(jié)構(gòu)內(nèi)存占用
根據(jù)上面的內(nèi)存占用計(jì)算規(guī)則,可以計(jì)算出一個(gè)對(duì)象在內(nèi)存中的占用空間大小情況,下面舉例分析下Java中的Enum, ArrayList及HashMap的內(nèi)存占用情況,讀者可以仿照分析計(jì)算過程來計(jì)算其他數(shù)據(jù)結(jié)構(gòu)的內(nèi)存占用情況。
注: 下面的分析計(jì)算基于HotSpot Jvm, JDK1.8, 64位機(jī)器,開啟指針壓縮。
枚舉類創(chuàng)建enum時(shí),編譯器會(huì)生成一個(gè)相關(guān)的類,這個(gè)類繼承自java.lang.Enum。Enum類擁有兩個(gè)屬性變量,分別為int的ordinal和String的name, 相關(guān)源碼如下:
public abstract class Enum> implements Comparable , Serializable { /** * The name of this enum constant, as declared in the enum declaration. * Most programmers should use the {@link #toString} method rather than * accessing this field. */ private final String name; /** * The ordinal of this enumeration constant (its position * in the enum declaration, where the initial constant is assigned * an ordinal of zero). * * Most programmers will have no use for this field. It is designed * for use by sophisticated enum-based data structures, such as * {@link java.util.EnumSet} and {@link java.util.EnumMap}. */ private final int ordinal; }
以下面的TestEnum為例進(jìn)行枚舉類的內(nèi)存占用分析
public enum TestEnum { ONE(1, "one"), TWO(2, "two"); private int code; private String desc; TestEnum(int code, String desc) { this.code = code; this.desc = desc; } public int getCode() { return code; } public String getDesc() { return desc; } }
這里TestEnum的每個(gè)實(shí)例除了父類的兩個(gè)屬性外,還擁有一個(gè)int的code及String的desc屬性,所以一個(gè)TestEnum的實(shí)例本身所占用的內(nèi)存大小為:
12(header) + 4(ordinal) + 4(name reference) + 4(code) + 4(desc reference) = 28(padding) -> 32 bytes.
總共占用的內(nèi)存大小為:
按照上面對(duì)字符串類型的分析,desc和name都占用:48 bytes。
所以TestEnum.ONE占用總內(nèi)存大小為:
12(header) + 4(ordinal) + 4(code) + 48 * 2(desc, name) + 4(desc reference) + 4(name reference) = 128 (bytes)
JProfiler中的結(jié)果可以驗(yàn)證上述分析:
在分析ArrayList的內(nèi)存之前,有必須先了解下ArrayList的實(shí)現(xiàn)原理。
ArrayList實(shí)現(xiàn)List接口,底層使用數(shù)組保存所有元素。其操作基本上是對(duì)數(shù)組的操作。下面分析下源代碼:
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access
ArrayList提供了三種方式的構(gòu)造器,可以構(gòu)造一個(gè)默認(rèn)的空列表、構(gòu)造一個(gè)指定初始容量的空列表及構(gòu)造一個(gè)包含指定collection元素的列表,這些元素按照該collection的迭代器返回它們的順序排列。
/** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * Constructs an empty list with the specified initial capacity. * * @ initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection"s * iterator. * * @ c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection extends E> c)等,這里著重介紹一下add(E e)方法。
/** * Appends the specified element to the end of this list. * * @ e element to be appended to this list * @return true (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
add方法將指定的元素添加到此列表的尾部。這里注意下ensureCapacityInternal方法,這個(gè)方法會(huì)檢查添加后元素的個(gè)數(shù)是否會(huì)超過當(dāng)前數(shù)組的長(zhǎng)度,如果超出,數(shù)組將會(huì)進(jìn)行擴(kuò)容。
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Increases the capacity of this ArrayList instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @ minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It"s already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
如果初始時(shí)沒有指定ArrayList大小,在第一次調(diào)用add方法時(shí),會(huì)初始化數(shù)組默認(rèn)最小容量為10。看下grow方法的源碼:
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @ minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
從上述代碼可以看出,數(shù)組進(jìn)行擴(kuò)容時(shí),會(huì)將老數(shù)組中的元素重新拷貝一份到新的數(shù)組中,每次數(shù)組擴(kuò)容的增長(zhǎng)是原容量的1.5倍。這種操作的代價(jià)是很高的,因此在實(shí)際使用時(shí),應(yīng)該盡量避免數(shù)組容量的擴(kuò)張。當(dāng)可預(yù)知要保存的元素的數(shù)量時(shí),要在構(gòu)造ArrayList實(shí)例時(shí),就指定其容量,以避免數(shù)組擴(kuò)容的發(fā)生。或者根據(jù)實(shí)際需求,通過調(diào)用ensureCapacity方法來手動(dòng)增加ArrayList實(shí)例的容量。
ArrayList其他操作讀取刪除等原理這里不作介紹了。
下面開始分析ArrayList的內(nèi)存占用情況。ArrayList繼承AbstractList類,AbstractList擁有一個(gè)int類型的modCount屬性,ArrayList本身擁有一個(gè)int類型的size屬性和一個(gè)數(shù)組屬性。
所以一個(gè)ArrayList實(shí)例本身的的大小為:
12(header) + 4(modCount) + 4(size) + 4(elementData reference) = 24 (bytes)
下面分析一個(gè)只有一個(gè)Integer(1)元素的ArrayList
ArrayListtestList = Lists.newArrayList(); testList.add(1);
根據(jù)上面對(duì)ArrayList原理的介紹,當(dāng)調(diào)用add方法時(shí),ArrayList會(huì)初始化一個(gè)默認(rèn)大小為10的數(shù)組,而數(shù)組中保存的Integer(1)實(shí)例大小為16 bytes。
則testList占用的內(nèi)存大小為:
24(ArrayList itselft) + 16(elementData array header) + 10 * 4(elemetData reference) + 16(Integer) = 96 (bytes)
JProfiler中的結(jié)果驗(yàn)證了上述分析:
HashMap要分析HashMap的內(nèi)存占用,同樣需要先了解HashMap的實(shí)現(xiàn)原理。
HashMap是一個(gè)“鏈表散列”的數(shù)據(jù)結(jié)構(gòu),即數(shù)組和鏈表的結(jié)合體。
從圖上可以看出,HashMap底層是一個(gè)數(shù)組結(jié)構(gòu),數(shù)組中的每一項(xiàng)又是一個(gè)鏈表。當(dāng)新建一個(gè)HashMap的時(shí)候,初始化一個(gè)數(shù)組,源碼如下:
/** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ transient Node[] table;
Node是鏈表中一個(gè)結(jié)點(diǎn),一個(gè)Node對(duì)象保存了一對(duì)HashMap的Key,Value以及指向下一個(gè)節(jié)點(diǎn)的指針,源碼如下:
/** * Basic hash bin node, used for most entries. (See below for * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) */ static class Nodeimplements Map.Entry { final int hash; final K key; V value; Node next; Node(int hash, K key, V value, Node next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } }
HashMap提供了四種方式的構(gòu)造器,分別為指定初始容量及負(fù)載因子構(gòu)造器,指定初始容量構(gòu)造器,不指定初始容量及負(fù)載因子構(gòu)造器,以及根據(jù)已有Map生成新Map的構(gòu)造器。
/** * Constructs an empty HashMap with the specified initial * capacity and load factor. * * @ initialCapacity the initial capacity * @ loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } /** * Constructs an empty HashMap with the specified initial * capacity and the default load factor (0.75). * * @ initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty HashMap with the default initial capacity * (16) and the default load factor (0.75). */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * Constructs a new HashMap with the same mappings as the * specified Map. The HashMap is created with * default load factor (0.75) and an initial capacity sufficient to * hold the mappings in the specified Map. * * @ m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ public HashMap(Map extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
如果不指定初始容量及負(fù)載因子,默認(rèn)的初始容量為16, 負(fù)載因子為0.75。
負(fù)載因子衡量的是一個(gè)散列表的空間的使用程度,負(fù)載因子越大表示散列表的裝填程度越高,反之愈小。對(duì)于使用鏈表法的散列表來說,查找一個(gè)元素的平均時(shí)間是O(1+a),因此如果負(fù)載因子越大,對(duì)空間的利用更充分,然而后果是查找效率的降低;如果負(fù)載因子太小,那么散列表的數(shù)據(jù)將過于稀疏,對(duì)空間造成嚴(yán)重浪費(fèi)。
HashMap有一個(gè)容量閾值屬性threshold,是根據(jù)初始容量和負(fù)載因子計(jì)算得出threshold=capacity*loadfactor, 如果HashMap中數(shù)組元素的個(gè)數(shù)超過這個(gè)閾值,則HashMap會(huì)進(jìn)行擴(kuò)容。HashMap底層的數(shù)組長(zhǎng)度總是2的n次方,每次擴(kuò)容容量為原來的2倍。
擴(kuò)容的目的是為了減少hash沖突,提高查詢效率。而在HashMap數(shù)組擴(kuò)容之后,最消耗性能的點(diǎn)就出現(xiàn)了:原數(shù)組中的數(shù)據(jù)必須重新計(jì)算其在新數(shù)組中的位置,并放進(jìn)去,這就是resize。
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } /** * Implements Map.put and related methods * * @ hash hash for key * @ key the key * @ value the value to put * @ onlyIfAbsent if true, don"t change existing value * @ evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab; Node p; int n, i; //初始化數(shù)組的大小為16,容量閾值為16*0.75=12 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //如果key的hash值對(duì)應(yīng)的數(shù)組位置沒有元素,則新建Node放入此位置 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode )p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
從上面的源代碼中可以看出:當(dāng)我們往HashMap中put元素的時(shí)候,先根據(jù)key的hashCode重新計(jì)算hash值,根據(jù)hash值得到這個(gè)元素在數(shù)組中的位置(即下標(biāo)),如果數(shù)組該位置上已經(jīng)存放有其他元素了,那么在這個(gè)位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。如果數(shù)組該位置上沒有元素,就直接將該元素放到此數(shù)組中的該位置上。
關(guān)于HashMap數(shù)據(jù)的讀取這里就不作介紹了。
這里分析一個(gè)只有一組鍵值對(duì)的HashMap, 結(jié)構(gòu)如下:
MaptestMap = Maps.newHashMap(); testMap.put(1, 2);
首先分析HashMap本身的大小。HashMap對(duì)象擁有的屬性包括:
/** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ transient Node[] table; /** * Holds cached entrySet(). Note that AbstractMap fields are used * for keySet() and values(). */ transient Set > entrySet; /** * The number of key-value mappings contained in this map. */ transient int size; /** * The number of times this HashMap has been structurally modified * Structural modifications are those that change the number of mappings in * the HashMap or otherwise modify its internal structure (e.g., * rehash). This field is used to make iterators on Collection-views of * the HashMap fail-fast. (See ConcurrentModificationException). */ transient int modCount; /** * The next size value at which to resize (capacity * load factor). * * @serial */ // (The javadoc description is true upon serialization. // Additionally, if the table array has not been allocated, this // field holds the initial array capacity, or zero signifying // DEFAULT_INITIAL_CAPACITY.) int threshold; /** * The load factor for the hash table. * * @serial */ final float loadFactor;
HashMap繼承了AbstractMap
transient SetkeySet; transient Collection values;
所以一個(gè)HashMap對(duì)象本身的大小為:
12(header) + 4(table reference) + 4(entrySet reference) + 4(size) + 4(modCount) + 4(threshold) + 8(loadFactor) + 4(keySet reference) + 4(values reference) = 48(bytes)
接著分析testMap實(shí)例在總共占用的內(nèi)存大小。
根據(jù)上面對(duì)HashMap原理的介紹,可知每對(duì)鍵值對(duì)對(duì)應(yīng)一個(gè)Node對(duì)象。根據(jù)上面的Node的數(shù)據(jù)結(jié)構(gòu),一個(gè)Node對(duì)象的大小為:
12(header) + 4(hash reference) + 4(key reference) + 4(value reference) + 4(next pointer reference) = 28 (padding) -> 32(bytes)
加上Key和Value兩個(gè)Integer對(duì)象,一個(gè)Node占用內(nèi)存總大小為:32 + 2 * 16 = 64(bytes)
JProfiler中結(jié)果:
下面分析HashMap的Node數(shù)組的大小。
根據(jù)上面HashMap的原理可知,在不指定容量大小的情況下,HashMap初始容量為16,所以testMap的Node[]占用的內(nèi)存大小為:
16(header) + 16 * 4(Node reference) + 64(Node) = 144(bytes)
JProfile結(jié)果:
所以,testMap占用的內(nèi)存總大小為:
48(map itself) + 144(Node[]) = 192(bytes)
JProfile結(jié)果:
這里只用一個(gè)例子說明如何對(duì)HashMap進(jìn)行占用內(nèi)存大小的計(jì)算,根據(jù)HashMap初始化容量的大小,以及擴(kuò)容的影響,HashMap占用內(nèi)存大小要進(jìn)行具體分析,
不過思路都是一致的。
以上對(duì)計(jì)算Java對(duì)象占用內(nèi)存的基本規(guī)則及方法進(jìn)行了介紹,并通過分析枚舉類,ArrayList, HashMap的內(nèi)存占用情況介紹了分析復(fù)雜對(duì)象內(nèi)存占用的基本方法,
實(shí)際工作中需要精確計(jì)算Java對(duì)象內(nèi)存占用情況的場(chǎng)景不多,不過作為Java程序員的基本素養(yǎng),了解這方面內(nèi)容還是很有必要的。
An overview of memory saving techniques in Java
Memory consumption of popular Java data types – part 1
Memory consumption of popular Java data types – part 2
memory usage compare hashmap, arraylist, and array
How does a HashMap work in JAVA
一個(gè)Java對(duì)象到底占用多大內(nèi)存?
深入Java集合學(xué)習(xí)系列:HashMap的實(shí)現(xiàn)原理
深入Java集合學(xué)習(xí)系列:ArrayList的實(shí)現(xiàn)原理
《Java編程思想》
《深入理解Java虛擬機(jī)》
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/65167.html
摘要:堆內(nèi)存使用分析,垃圾收集器日志解讀重要的東東在中,對(duì)象實(shí)例都是在堆上創(chuàng)建。機(jī)制是由提供,用來清理需要清除的對(duì)象,回收堆內(nèi)存。在中,是由一個(gè)被稱為垃圾回收器的守護(hù)線程執(zhí)行的。 堆內(nèi)存使用分析,垃圾收集器 GC 日志解讀 重要的東東 在Java中,對(duì)象實(shí)例都是在堆上創(chuàng)建。一些類信息,常量,靜態(tài)變量等存儲(chǔ)在方法區(qū)。堆和方法區(qū)都是線程共享的。 GC機(jī)制是由JVM提供,用來清理需要清除的對(duì)象,...
摘要:小心遞歸中內(nèi)存泄漏前段時(shí)間由于業(yè)務(wù)需要,需要從數(shù)據(jù)庫中查詢出來所有滿足條件的數(shù)據(jù),然后導(dǎo)入到文件中。綜上,我們可以得知程序出現(xiàn)了內(nèi)存泄漏。 小心遞歸中內(nèi)存泄漏 前段時(shí)間由于業(yè)務(wù)需要,需要從數(shù)據(jù)庫中查詢出來所有滿足條件的數(shù)據(jù),然后導(dǎo)入到文件中。于是隨便寫了個(gè)程序,查詢出所有滿足條件然后再寫入文件。但是實(shí)際上線后卻發(fā)現(xiàn),程序剛開始運(yùn)行馬上看到部分?jǐn)?shù)據(jù)寫入到文件,但是后面運(yùn)行越來越慢,于是對(duì)...
摘要:分析應(yīng)用靜息態(tài)內(nèi)存占用。這里采用的方式是靜息態(tài)內(nèi)存進(jìn)入,立即內(nèi)存操作一段時(shí)間之后再內(nèi)存一共有三次,可以利用對(duì)比的功能對(duì)比內(nèi)存增量。 作者:楊超,騰訊移動(dòng)客戶端開發(fā) 工程師 商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系騰訊WeTest獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。原文鏈接:http://wetest.qq.com/lab/view/359.html WeTest 導(dǎo)讀 歷時(shí)五天的內(nèi)存優(yōu)化已經(jīng)結(jié)束,這里總結(jié)一下這幾天...
摘要:內(nèi)存溢出分配的內(nèi)存空間超過系統(tǒng)內(nèi)存。內(nèi)存泄漏的原因分析由大塊組成堆,棧,本地方法棧,程序計(jì)數(shù)器,方法區(qū)。內(nèi)存溢出的原因分析內(nèi)存溢出是由于沒被引用的對(duì)象垃圾過多造成沒有及時(shí)回收,造成的內(nèi)存溢出。小結(jié)棧內(nèi)存溢出程序所要求的棧深度過大導(dǎo)致。 showImg(https://segmentfault.com/img/bVbweuq?w=563&h=300); 前言:JVM中除了程序計(jì)數(shù)器,其他...
閱讀 1377·2021-10-08 10:04
閱讀 2681·2021-09-22 15:23
閱讀 2724·2021-09-04 16:40
閱讀 1172·2019-08-29 17:29
閱讀 1492·2019-08-29 17:28
閱讀 2988·2019-08-29 14:02
閱讀 2221·2019-08-29 13:18
閱讀 838·2019-08-23 18:35