国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java對(duì)象內(nèi)存占用分析

JouyPub / 3595人閱讀

摘要:對(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
數(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 + 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

在分析ArrayList的內(nèi)存之前,有必須先了解下ArrayList的實(shí)現(xiàn)原理。
ArrayList實(shí)現(xiàn)List接口,底層使用數(shù)組保存所有元素。其操作基本上是對(duì)數(shù)組的操作。下面分析下源代碼:

1. 底層使用數(shù)組保存數(shù)據(jù):
    /**
     * 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
2. 構(gòu)造方法:

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 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;
        }
    }
3. 存儲(chǔ):

ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection 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其他操作讀取刪除等原理這里不作介紹了。

4. 內(nèi)存占用

下面開始分析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實(shí)例占用的內(nèi)存大小。

   ArrayList testList = 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)原理。

1. HashMap的數(shù)據(jù)結(jié)構(gòu)

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 Node implements 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;
        }
     }
2. 構(gòu)造方法

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 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。

3. 數(shù)據(jù)的存儲(chǔ)
    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ù)的讀取這里就不作介紹了。

4. HashMap內(nèi)存占用

這里分析一個(gè)只有一組鍵值對(duì)的HashMap, 結(jié)構(gòu)如下:

    Map testMap = 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, AbstractMap有兩個(gè)屬性:

     transient Set        keySet;
     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)行具體分析,
不過思路都是一致的。

總結(jié)

以上對(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

相關(guān)文章

  • 《深入理解Java虛擬機(jī)》(六)堆內(nèi)存使用分析,垃圾收集器 GC 日志解讀

    摘要:堆內(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ì)象,...

    CODING 評(píng)論0 收藏0
  • 小心遞歸中內(nèi)存泄漏

    摘要:小心遞歸中內(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ì)...

    layman 評(píng)論0 收藏0
  • 我這樣減少了26.5M Java內(nèi)存

    摘要:分析應(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é)一下這幾天...

    Miracle 評(píng)論0 收藏0
  • 關(guān)于JVM內(nèi)存溢出的原因分析及解決方案探討

    摘要:內(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ù)器,其他...

    xuexiangjys 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<