为什么会写这边博文呢?因为笔者最近在看《深入理解Java虚拟机》,并且刷了一个大公司的笔试题。其中比较多的是问如int在32位jvm和64位jvm中分别占用多少字节,算一个对象占用多少内存,今天我们就通过例子来验证一下,总结出一般规律!
在网上参考了一篇很老的博文http://www.jroller.com/maxim/entry/again_about_determining_size_of。里面给出了如果计算一个对象占用多少内存的代码:
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
/**
* 对象占用字节大小工具类
*
* @author tianmai.fh
* @date 2014-03-18 11:29
*/
public class SizeOfObject {
static Instrumentation inst;
public static void premain(String args, Instrumentation instP) {
inst = instP;
}
/**
* 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br>
* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br>
* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br>
*
* @param obj
* @return
*/
public static long sizeOf(Object obj) {
return inst.getObjectSize(obj);
}
/**
* 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小
*
* @param objP
* @return
* @throws IllegalAccessException
*/
public static long fullSizeOf(Object objP) throws IllegalAccessException {
Set<Object> visited = new HashSet<Object>();
Deque<Object> toBeQueue = new ArrayDeque<Object>();
toBeQueue.add(objP);
long size = 0L;
while (toBeQueue.size() > 0) {
Object obj = toBeQueue.poll();
//sizeOf的时候已经计基本类型和引用的长度,包括数组
size += skipObject(visited, obj) ? 0L : sizeOf(obj);
Class<?> tmpObjClass = obj.getClass();
if (tmpObjClass.isArray()) {
//[I , [F 基本类型名字长度是2
if (tmpObjClass.getName().length() > 2) {
for (int i = 0, len = Array.getLength(obj); i < len; i++) {
Object tmp = Array.get(obj, i);
if (tmp != null) {
//非基本类型需要深度遍历其对象
toBeQueue.add(Array.get(obj, i));
}
}
}
} else {
while (tmpObjClass != null) {
Field[] fields = tmpObjClass.getDeclaredFields();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers()) //静态不计
|| field.getType().isPrimitive()) { //基本类型不重复计
continue;
}
field.setAccessible(true);
Object fieldValue = field.get(obj);
if (fieldValue == null) {
continue;
}
toBeQueue.add(fieldValue);
}
tmpObjClass = tmpObjClass.getSuperclass();
}
}
}
return size;
}
/**
* String.intern的对象不计;计算过的不计,也避免死循环
*
* @param visited
* @param obj
* @return
*/
static boolean skipObject(Set<Object> visited, Object obj) {
if (obj instanceof String && obj == ((String) obj).intern()) {
return true;
}
return visited.contains(obj);
}
}
我也用了它这个工具,但是在使用过程中遇到的一些坑点也在这里说明一下,避免大家入坑。
步骤一:新建一个web项目或者一个普通java项目也行。然后编写上述代码,并且修改web项目自带的MANIFEST.MF文件如下:
Manifest-Version: 1.0
Premain-Class: size.SizeOfObject //这里千万要注意文档的格式,我前期就犯了这个错误。
Can-Redefine-Classes: false //key: value 注意value前面的空格 然后Premain-Class:为你的代码所在的路径 包名+文件名
步骤二 :把这个项目打包为jar包。我通过eclipse打包
步骤三:当你打包好了jar包后就可以在我们的代码中使用这段代码了,把jar引入进来就可以工作了。
下面正式来探讨我们刚才的问题!
我们先回顾一下jvm中java对象的内存布局:对象头(header),实例数据(Instance Data)和对其填充(Padding).
对象头
对象头在64位jvm中占用16bytes,在32位jvm中占用8bytes
import size.SizeOfObject;
public class Size {
//测试代码
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(SizeOfObject.sizeOf(new Object()));
}
}
运行时在VM arguments中添加
-javaagent:C:\Users\hcl\Desktop\sizeofObject1.jar
-XX:-UseCompreessedOops //不使用指针压缩
上面代码在64位jvm中运行结果为16,32位jvm中为8(使用指针压缩)。
-XX:+UseCompreessedOops //默认情况使用指针压缩
如果使用指针压缩对象头在64位jvm中是12bytes,指针压缩对32位jvm无效。
实例数据
原生类型(primitive type)的内存占用如下:
原生类型 | 内存占用 |
---|---|
byte,boolean | 1byte |
short,char | 2bytes |
int,float | 4bytes |
long,double | 8bytes |
对象引用(reference)在64位jvm上占用8字节,在32为jvm上占用4字节,指针压缩只是针对64位jvm,对32位jvm无效,使用指针压缩对象引用在64位jvm上占用4字节。
对齐填充
Java对象占用空间是8字节对齐的,即所有Java对象占用的字节数必须是8的倍数(只是对象不包括原生类型)这样便于内存管理,便于寻址
所以Java对象占用的总内存为 (对象头+实例数据+对齐填充),其中满足条件(对象头+实例数据+对齐填充)%8==0&&0<=对齐填充<8
Java对象内存占用分析
包装类型:
包装类型占用内存的大小等于对象头大小加上底层基础数据类型的大小再加padding。
包装类型 | 内存占用(不使用压缩) | 内存占用(使用压缩) |
---|---|---|
Byte,Boolean | 24byte | 16byte |
Short,Character | 24bytes | 16bytes |
Integer,Float | 24bytes | 16bytes |
Long,Double | 24bytes | 24bytes |
数组类型:
64位jvm上,数组对象的对象头占用24字节,启动压缩后占用16字节。比普通对象占用内存多是因为需要额外的空间存储数组的长度。数组对象占用内存大小为:对象头+引用类型占用*length
int[5]:不开启压缩:24+5*8=64对齐填充后64(64位jvm)
int[5]:开启压缩:16+5*4=36对齐填充后40(64位jvm)
int[5]:不开启压缩:12+5*4=32对齐填充后32(32位jvm)
String[5]:不开启压缩:24+5*8=64对齐填充后64(64位jvm)
String[5]:开启压缩:16+5*4=36对齐填充后40(64位jvm)
String[5]:不开启压缩:12+5*4=32对齐填充后32(32位jvm)
String类型:
在String源码当中包含2个属性,一个用于存放字符串数据的char[], 一个int类型的hashcode。
所以String类型占用内存如下:
String str = new String(); 16(header)+8(reference)+4(int)= 28 对齐填充后32(64jvm不压缩指针)
String str = new String(); 12(header)+4(reference)+4(int)= 20 对齐填充后24(64jvm压缩指针)
String str = new String(); 8(header)+4(reference)+4(int)= 16 对齐填充后16(32jvm)
总结:
参考:
1、http://www.jroller.com/maxim/entry/again_about_determining_size_of
2、http://www.cnblogs.com/magialmoon/p/3757767.html