一个Java对象到底占用多大内存

Python014

一个Java对象到底占用多大内存,第1张

Object o=new Object():

在java中空对象占八个字节,对象的引用占四个字节。所以上面那条语句所占的空间是4byte+8byte=12byte.java中的内存是以8的倍数来分配的,所以分配的内存是16byte.

举个例子:

Class O{

int i

byte j

String s

}

其所占内存的大小是空对象(8)+int(4)+byte(1)+String引用(4)=17byte,因要是8的整数倍,所以其占大小为24byte.

当然,如果类里有其他对象的话,也要把其他对象的空间算进去

对象头

对象头在32位系统上占用8bytes,64位系统上占用16bytes。

实例数据

原生类型(primitive type)的内存占用如下:

Primitive TypeMemory Required(bytes)

boolean1

byte1

short2

char2

int4

float4

long8

double8

reference类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes。

对齐填充

HotSpot的对齐方式为8字节对齐:

(对象头 + 实例数据 + padding) % 8等于0且0 <= padding <8

指针压缩

对象占用的内存大小收到VM参数UseCompressedOops的影响。

1)对对象头的影响

开启(-XX:+UseCompressedOops)对象头大小为12bytes(64位机器)。

static class A {

int a

}

首先,我们先写一段大家可能不怎么写或者认为不可能的代码:一个类中,几个类型都是private类型,没有public方法,如何对这些属性进行读写操作,看似不可能哦,为什么,这违背了面向对象的封装,其实在必要的时候,留一道后门可以使得语言的生产力更加强大,对象的序列化不会因为没有public方法就无法保存成功吧,OK,我们简单写段代码开个头,逐步引入到怎么样去测试对象的大小,一下代码非常简单,相信不用我解释什么:

import java.lang.reflect.Field

class NodeTest1 {

private int a = 13

private int b = 21

}

public class Test001 {

public static void main(String []args) {

NodeTest1 node = new NodeTest1()

Field []fields = NodeTest1.class.getDeclaredFields()

for(Field field : fields) {

field.setAccessible(true)

try {

int i = field.getInt(node)

field.setInt(node, i * 2)

System.out.println(field.getInt(node))

} catch (IllegalArgumentException e) {

e.printStackTrace()

} catch (IllegalAccessException e) {

e.printStackTrace()

}

}

}

}

代码最基本的意思就是:实例化一个NodeTest1这个类的实例,然后取出两个属性,分别乘以2,然后再输出,相信大家会认为这怎么可能,NodeTest1根本没有public方法,代码就在这里,将代码拷贝回去运行下就OK了,OK,现在不说这些了,运行结果为:

26

42

为什么可以取到,是每个属性都留了一道门,主要是为了自己或者外部接入的方便,相信看代码自己仔细的朋友,应该知道门就在:field.setAccessible(true)代表这个域的访问被打开,好比是一道后门打开了,呵呵,上面的方法如果不设置这个,就直接报错。

看似和对象大小没啥关系,不过这只是抛砖引玉,因为我们首先要拿到对象的属性,才能知道对象的大小,对象如果没有提供public方法我们也要知道它有哪些属性,所以我们后面多半会用到这段类似的代码哦!

对象测量大小的方法关键为java提供的(1.5过后才有):java.lang.instrument.Instrumentation,它提供了丰富的对结构的等各方面的跟踪和对象大小的测量的API(本文只阐述对象大小的测量方法),于是乎我心喜了,不过比较恶心的是它是实例化类:sun.instrument.IntrumentationImpl是sun开头的,这个鬼东西有点不好搞,翻开源码构造方法是private类型,没有任何getInstance的方法,写这个类干嘛?看来这个只能被JVM自己给初始化了,那么怎么将它自己初始化的东西取出来用呢,唯一能想到的就是agent代理,那么我们先抛开代理,首先来写一个简单的对象测量方法:

步骤1:(先创建一个用于测试对象大小的处理类)

import java.lang.instrument.Instrumentation

public class MySizeOf {

private static Instrumentation inst

/**

*这个方法必须写,在agent调用时会被启用

*/

public static void premain(String agentArgs, Instrumentation instP) {

inst = instP

}

/**

* 直接计算当前对象占用空间大小,包括:当前类及超类的基本类型实例字段大小

* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小

* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小

* 用来测量java对象的大小(这里先理解这个大小是正确的,后面再深化)

*/

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)

}

}

步骤2:上面我们写好了agent的代码,此时我们要将上面这个类编译后打包为一个jar文件,并且在其包内部的META-INF/MANIFEST.MF文件中增加一行:Premain-Class: MySizeOf代表执行代理的全名,这里的类名称是没有package的,如果你有package,那么就写全名,我们这里假设打包完的jar包名称为agent.jar(打包过程这里简单阐述,就不细说了),OK,继续向下走:

步骤3:编写测试类,测试类中写:

public class TestSize {

public static void main(String []args) {

System.out.println(MySizeOf.sizeOf(new Integer(1)))

System.out.println(MySizeOf.sizeOf(new String("a")))

System.out.println(MySizeOf.sizeOf(new char[1]))

}

}