学过c++的朋友想必对“c++的内存对齐规则”不会感到陌生。那么在java中,是否也存在这样的规则呢?为此,笔者进行了实验,发现java的内存对齐规则非常简明——8字节对齐。现与大家分享下。
一个java对象所占的内存空间分为三个部分:对象头(Mark Word+类型指针+长度)+实例数据+填充。
填充的目的是保证一个java对象的大小是8B的整数倍。
对象头分为三个部分:Mark Word、类型指针和长度。这三个部分所占内存空间大小如下所示:
项目 | 占用的内存大小 | 说明 |
---|---|---|
Mark Word | 4B/32位, 8B/64位 | |
类型指针 | 4B/32位, 8B/64位 | |
长度 | 4B/32位, 8B/64位 | 仅数组对象才会有这一部分 |
接下来说一下实例数据包括哪些。静态成员变量或静态成员常量是属于整个类的,存储在方法区,所以不包括在内;当前类所实现接口中的常量,是静态的,同样存储在方法区,不包括在内;所有的方法,不管是静态的还是非静态的,也存储在方法区,不包括在内。剩下的实例成员变量或实例成员常量(包括其祖先类的)就是每个对象要存储的数据 。这些实例成员所占的内存空间大小如下所示:
类型 | 占用的内存大小 | 说明 |
---|---|---|
boolean | 1B | 与平台无关 |
byte | 1B | 与平台无关 |
short | 2B | 与平台无关 |
char | 2B | 与平台无关 |
int | 4B | 与平台无关 |
float | 4B | 与平台无关 |
long | 8B | 与平台无关 |
double | 8B | 与平台无关 |
reference | 4B/32位,8B/64位 |
具体计算方式如下:
假设当前类为B,其父类为A3,类A3的父类为A2,类A2的父类为A1,类A1的父类是Object,且类B, A3, A2, A1都仅有一个char c;
成员变量(当然,这仅仅是举例说明而已,实际情况下可更复杂或更简单),用S_XXX表示类XXX中实例成员的总大小(不包括其祖先类的)。那么类B的一个对象b所占据的内存大小(包括祖先类的)sizeof_b为:
sizeof_b=对象头+(S_A1+填充S_A1)+(S_A2+填充S_A2)+(S_A3+填充_S_A3)+(S_B+填充_S_B)。
“填充_S_XXX”的目的是保证从“对象头”到类XXX部分之和为8B的整数倍。注意看啊,这里每个类都是要填充的,而不是仅在最后才填充,这样做也方便强制类型转换后的处理。而且类XXX中成员变量的声明顺序不影响“S_XXX”的值。
上述类B的一个对象b所占用的内存结构图(32位机器上)如下所示:
又比如“new int[4]
”这个对象在32位机器上所占用的内存为:
下面给出笔者的实验步骤。先是编写两个类SizeOfObjectTest和SizeOfAgent;然后将代码(使用Ant工具)打成可执行的jar包(如 jvm-project.jar,主类是SizeOfObjectTest),最后在命令行里输入如下命令运行该jar包即可查看结果:
java -javaagent:jvm-project.jar -XX:-UseCompressedOops -jar jvm-project.jar
本文仅仅讨论未使用指针压缩情况下的占用内存大小情况。
类SizeOfAgent的编写以及运行方式均是参考http://yueyemaitian.iteye.com/blog/2033046。
下面分别给出SizeOfObjectTest类、SizeOfAgent类和打包脚本。
SzieOfObjectTest类的代码:
package quote;
import java.io.File;
/**
* @author tianmai.fh
* @date 2014-03-18 20:17
*/
public class SizeOfObjectTest {
/**
*
* -XX:-UseCompressedOops: mark/8 + metedata/8 + 8 + padding/0 = 24
*/
static class A {
int a;
char c;
}
static class A2{
char b;
int a;
}
static class A3{
char b;
boolean c;
double d;
short e;
boolean f;
}
static class A4{
char b;
short e;
boolean d;
boolean f;
double a;
}
static class A5{
int[] b=new int[10];
}
/**
* -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24
* -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + 4 = 24
*/
static class B {
int a;
int b;
}
/**
* -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24
* -XX:-UseCompressedOops: mark/8 + metedata/8 + 8 + 4 + padding/4 = 32
*/
static class B2 {
int b2a;
Integer b2b;
}
/**
* 不考虑对象头:
* 4 + 4 + 4 * 3 + 3 * sizeOf(B)
*/
static class C extends A {
int ba;
B[] as = new B[3];
C() {
for (int i = 0; i < as.length; i++) {
as[i] = new B();
}
}
}
static class A1{
private double a;
// char b;
public void setA1(){
a=10;
}
public void showA1(){
System.out.println("A1 a: "+a);
}
}
static class C1 extends A1{
double ba;
private double a;
B[] as=new B[3];
public void set_C1(){
a=100;
}
public void show_C1(){
System.out.println("C1 a: "+a);
}
}
static class D extends B {
int da;
Integer[] di = new Integer[3];
}
/**
* 会算上A的实例字段
*/
static class E extends A {
int ea;
int eb;
}
static class AA extends A{
int a;
char c;
}
static class AAA extends AA{
int a;
char c;
}
public static void main(String[] args) throws IllegalAccessException {
C1 c1=new C1();
c1.setA1();
c1.showA1();
c1.set_C1();
c1.showA1();
c1.show_C1();
System.out.println(new File("./target/classes").getAbsolutePath());
System.out.println("sizeOf(new Object())=" + SizeOfAgent.sizeOf(new Object()));
System.out.println("sizeOf(new A())=" + SizeOfAgent.sizeOf(new A()));
System.out.println("sizeOf(new A1())=" + SizeOfAgent.sizeOf(new A1()));
System.out.println("sizeOf(new A3())=" + SizeOfAgent.sizeOf(new A3()));
System.out.println("sizeOf(new A4())=" + SizeOfAgent.sizeOf(new A4()));
System.out.println("sizeOf(new B())=" + SizeOfAgent.sizeOf(new B()));
System.out.println("sizeOf(new B2())=" + SizeOfAgent.sizeOf(new B2()));
System.out.println("sizeOf(new B[3])=" + SizeOfAgent.sizeOf(new B[3]));
System.out.println("sizeOf(new C())=" + SizeOfAgent.sizeOf(new C()));
System.out.println("sizeOf(new C1())=" + SizeOfAgent.sizeOf(new C1()));
System.out.println("fullSizeOf(new C())=" + SizeOfAgent.fullSizeOf(new C()));
System.out.println("sizeOf(new D())=" + SizeOfAgent.sizeOf(new D()));
System.out.println("fullSizeOf(new D())=" + SizeOfAgent.fullSizeOf(new D()));
System.out.println("sizeOf(new int[0])=" + SizeOfAgent.sizeOf(new int[0]));
System.out.println("sizeOf(new int[3])=" + SizeOfAgent.sizeOf(new int[3]));
System.out.println("sizeOf(new Integer(1))=" + SizeOfAgent.sizeOf(new Integer(1)));
System.out.println("sizeOf(new Integer[0])=" + SizeOfAgent.sizeOf(new Integer[0]));
System.out.println("sizeOf(new Integer[1])=" + SizeOfAgent.sizeOf(new Integer[1]));
System.out.println("sizeOf(new Integer[2])=" + SizeOfAgent.sizeOf(new Integer[2]));
System.out.println("sizeOf(new Integer[3])=" + SizeOfAgent.sizeOf(new Integer[3]));
System.out.println("sizeOf(new Integer[4])=" + SizeOfAgent.sizeOf(new Integer[4]));
System.out.println("sizeOf(new A[3])=" + SizeOfAgent.sizeOf(new A[3]));
System.out.println("sizeOf(new E())=" + SizeOfAgent.sizeOf(new E()));
System.out.println("sizeOf(new AA())=" + SizeOfAgent.sizeOf(new AA()));
System.out.println("sizeOf(new AAA())=" + SizeOfAgent.sizeOf(new AAA()));
}
}
类SizeOfAgent的代码:
/**
* 对象占用字节大小工具类
*
* @author tianmai.fh
* @date 2014-03-18 11:29
*/
public class SizeOfAgent {
static Instrumentation inst;
public static void premain(String args, Instrumentation instP) {
inst = instP;
}
/**
* 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、
* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;
* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小
*
* @param obj
* @return
*/
public static long sizeOf(Object obj) {
if(inst!=null){
return inst.getObjectSize(obj);
}
return 0L;
}
/**
* 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小
*
* @param objP
* @return
* @throws IllegalAccessException
*/
public static long fullSizeOf(Object objP) throws IllegalAccessException {
// Set
// Deque toBeQueue = new ArrayDeque<>();
// 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;
return 0;
}
/**
* String.intern的对象不计;计算过的不计,也避免死循环
*
* @param visited
* @param obj
* @return
*/
static boolean skipObject(Set visited, Object obj) {
if (obj instanceof String && obj == ((String) obj).intern()) {
return true;
}
return visited.contains(obj);
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("size of Object: "+SizeOfAgent.sizeOf(new Object()));
}
}
打包脚本build.xml的内容:
<project name="deepSearchHttpServer" basedir="." default="build-deepSearchHttpServer-runnable-jar">
<property name="src.dir" value="src" />
<property name="build.dir" value="build" />
<property name="version" value="1.0" />
<property name="lib.dir" value="lib" />
<path id="lib.classpath">
<fileset dir="${lib.dir}">
<include name="**/*.jar">
include>
fileset>
path>
<target name="init">
<delete dir="${build.dir}" />
<mkdir dir="${build.dir}" />
target>
<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${build.dir}" source="1.6" target="1.6" encoding="utf-8" debug="true" debuglevel="source,lines,vars">
<classpath refid="lib.classpath">
classpath>
javac>
<copy todir="${build.dir}">
<fileset dir="${src.dir}">
<include name="log4j.properties">
include>
fileset>
copy>
target>
<target name="build-deepSearchHttpServer-runnable-jar" depends="compile">
<pathconvert property="mf.classpath" pathsep=" ">
<mapper>
<chainedmapper>
<flattenmapper />
<globmapper from="*" to="lib/*" />
chainedmapper>
mapper>
<path refid="lib.classpath" />
pathconvert>
<delete file="jvm-project.jar" />
<jar destfile="jvm-project.jar" update="true" basedir="${build.dir}">
<manifest>
<attribute name="Main-class" value="quote.SizeOfObjectTest" />
<attribute name="Class-path" value="${mf.classpath}" />
<attribute name="Premain-class" value="quote.SizeOfAgent" />
<attribute name="Can-Redefine-Classes" value="false" />
manifest>
jar>
<delete dir="${build.dir}" />
target>
project>