目录
问题
代码实践
实验结果
Java对象内存布局
对象头:
实例数据:
对齐填充:
HashMap本身占用内存
Node占用内存
参考文章
本文内容来源于摘抄总结实践
kv都是 int,一个 int 4 byte,就是100w * 8那就是8M,或者存储的是包装类 Integer ,一个 Integer 16 byte,那就是32 M 咯?
通过java.lang.Runtime类中的freeMemory()方法来进行测试,输出freeMemory前,调用一次System.gc(),得出结论是百万键值对的HashMap占用大约69M空间。
package Java2;
import java.util.HashMap;
/**
* @author ranbo
* @version V1.0
* @Title:
* @Package Java2
* @Description:
* @date 2018/7/26 下午10:55
*/
public class HashMapUseMemory {
public static void main(String[] args) {
double start = 0;
double end = 0;
System.gc();
start = Runtime.getRuntime().freeMemory(); // 在java程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,但是java虚拟机100%的情况下是会稍微多挖一点的,
// 这些挖过来而又没有用上的内存,实际上就是freeMemory()
System.out.println("start: " + start);
System.out.println("end: " + end);
HashMap hashMap = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
hashMap.put(i, 128); // value 为128 和 127 就是int和Integer的区别,(16 byte - 4 byte) * 100w就是 12 M的样子 IntegerCache
if(i % 10000 == 0) {
end = Runtime.getRuntime().freeMemory();
System.out.println("start: " + start);
System.out.println("end: " + end);
System.out.println("HashMap对象占内存:" + (end - start) + " byte");
System.out.println("HashMap对象占内存:" + (end - start) / (1024 * 1024) + " M");
}
}
end = Runtime.getRuntime().freeMemory();
System.out.println("start: " + start);
System.out.println("end: " + end);
System.out.println("HashMap对象占内存:" + (end - start) + " byte");
System.out.println("HashMap对象占内存:" + (end - start) / (1024 * 1024) + " M");
System.out.println("----------清除了没有对象引用的对象"); // 即不可达对象
System.gc(); // 清除了没有对象引用的对象,比如里面的临时变量,个人觉得 hash 也是
end = Runtime.getRuntime().freeMemory();
System.out.println("start: " + start);
System.out.println("end: " + end);
System.out.println("HashMap对象占内存:" + (end - start) + " byte");
System.out.println("HashMap对象占内存:" + (end - start) / (1024 * 1024) + " M");
}
}
}
那接下来我们就来分析为啥会有这么多内存占用
Java对象的内存布局包括:对象头(Header),实例数据(Instance Data)和补齐填充(Padding)。
64位机器上,默认不开启指针压缩(-XX:-UseCompressedOops)的情况下,对象头占用12bytes,开启指针压缩(-XX:+UseCompressedOops)则占用16bytes。
原生类型(primitive type)的内存占用如下:
Primitive Type |
Memory Required(bytes) |
---|---|
byte, boolean |
1 byte |
short, char |
2 bytes |
int, float |
4 bytes |
long, double |
8 bytes |
对象引用(reference)类型在64位机器上,关闭指针压缩时占用8bytes, 开启时占用4bytes。
Java对象占用空间是8字节对齐的,即所有Java对象占用bytes数必须是8的倍数。例如,一个包含两个属性的对象:int和byte,并不是占用17bytes(12+4+1),而是占用24bytes(对17bytes进行8字节对齐)
/**
* 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对象本身的大小为:
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实例在总共占用的内存大小。
根据上面对HashMap原理的介绍,可知每对键值对对应一个Node对象。根据上面的Node的数据结构,一个Node对象的大小为:
/**
* 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;
}
}
12(header) + 4(hash reference) + 4(key reference) + 4(value reference) + 4(next pointer reference) = 28 (padding) -> 32(bytes)
加上Key和Value两个Integer对象,一个Node占用内存总大小为:32 + 2 * 16 = 64(bytes)
100w个就是 1000000 * 64
下面分析HashMap的Node数组的大小。
根据上面HashMap的原理可知,HashMap初为满足100w个node需要扩容到 1024 * 1024 * 2,这样乘以负载因子才会大于100w,所以Node[]占用的内存大小为:
Node[] = 16(header) + 1024 * 1024 * 2 * 4(Node reference) + 1000000 *64(Node)
所以,testMap占用的内存总大小为:
48(map itself) + (16 + (1024 * 1024 * 2 * 4) + 64 * 1000000)(Node[]) = = 72388672 byte
72388672 byte / (1024 *1024) = 69.03521728515625 M
一个1000万HashMap,会占用多少空间内存?
Java对象内存占用分析
Java中HashMap结构自身占用的内存和Runtime类的freeMemory()等几个方法