JVM的分代GC法非常适合处理小而短命的对象,这类对象可以很快的在minor GC后清除;但是日常开发中经常需要缓存一些对象,这些对象在经过几次MinorGC后最终会进入老年代;如果这些对象占用太多的内存则会出现OOM,所以应该限制缓存对象集合的内存占用,这样我们就需要计算出每个对象所占用的内存。
怎样计算一个对象的内存呢? 首先需要明白对象的内存结构。
对象的内存结构分为三大块,Head,Body和Padding。
Head:
class引用:Object中定义getClass()方法来获取对象的Class对象,大家知道class对象是保存在方法区的,也就是说对象中保存着指向class对象的引用,这个引用就在Head中。
一个引用在32位系统中占用4byte,在64位系统中占用8byte
MarkWord :Head中还存放一些其他信息:是否加锁,GC标志位,MinorGC次数,对象hashcode等,这部分空间通常叫作叫MarkWord(32位系统中占用4byte,在64位系统中占用8byte)。
- 如果对象是数组,head中还存储这数组的长度(4byte)
- Body:就是对象非static属性所占的空间 (static属性存放在方法区)
- Padding:java对象以8字节对齐在内存中,如果对象的占用不是8字节的倍数,则会补齐为8的倍数。-padding就是用来补齐的空间
知道了对象的内存结构,就可以计算出对象的内存占用
以一个64位系统中的Integer为例,
1. 计算Head区,8+8=16 (byte),
2. 计算Body区,查看Integer 的源码发现Integer类中只存在一个非static属性 private final int value; 所以Body占4字节,
3. 加入Padding,这样head+body=16+4=20,计算出的位数并不是8的倍数,所以加入4byte padding凑齐。
这样下来一个Integer占用的内存为Header+Body+Padding=16+4+4=24(byte)
对于Integer这种非常简单的类计算非常简单,如果类中存在继承关系,还需计算父类的属性。
这样计算太麻烦了,还好从JDK1.5以后引入了一个Instrumentation接口,调用
Instrumentation#getObjectSize(Object objectToSize)
就可以轻松的计算一个对象的内存大小。
下面是一个对于Instrumentation 对象的封装
/**
* Instrumentation agent used
* from : http://www.jroller.com/maxim/entry/again_about_determining_size_of
*/
public class SizeOfAgent {
static Instrumentation inst;
/** initializes agent */
public static void premain(String agentArgs, Instrumentation instP) {
inst = instP;
}
/**
* Returns object size without member sub-objects.
* @param o object to get size of
* @return object size
*/
public static long sizeOf(Object o) {
if(inst == null) {
throw new IllegalStateException("Can not access instrumentation environment.\n" +
"Please check if jar file containing SizeOfAgent class is \n" +
"specified in the java's \"-javaagent\" command line argument.");
}
return inst.getObjectSize(o);
}
/**
* Calculates full size of object iterating over
* its hierarchy graph.
* @param obj object to calculate size of
* @return object size
*/
public static long fullSizeOf(Object obj) {
Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
Stack<Object> stack = new Stack<Object>();
long result = internalSizeOf(obj, stack, visited);
while (!stack.isEmpty()) {
result += internalSizeOf(stack.pop(), stack, visited);
}
visited.clear();
return result;
}
private static boolean skipObject(Object obj, Map<Object, Object> visited) {
if (obj instanceof String) {
// skip interned string
if (obj == ((String) obj).intern()) {
return true;
}
}
return (obj == null) // skip visited object
|| visited.containsKey(obj);
}
private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited) {
if (skipObject(obj, visited)){
return 0;
}
visited.put(obj, null);
long result = 0;
// get size of object + primitive variables + member pointers
result += SizeOfAgent.sizeOf(obj);
// process all array elements
Class clazz = obj.getClass();
if (clazz.isArray()) {
if(clazz.getName().length() != 2) {// skip primitive type array
int length = Array.getLength(obj);
for (int i = 0; i < length; i++) {
stack.add(Array.get(obj, i));
}
}
return result;
}
// process all fields of the object
while (clazz != null) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (!Modifier.isStatic(fields[i].getModifiers())) {
if (fields[i].getType().isPrimitive()) {
continue; // skip primitive fields
} else {
fields[i].setAccessible(true);
try {
// objects to be estimated are put to stack
Object objectToAdd = fields[i].get(obj);
if (objectToAdd != null) {
stack.add(objectToAdd);
}
} catch (IllegalAccessException ex) {
assert false;
}
}
}
}
clazz = clazz.getSuperclass();
}
return result;
}
}