java 类的大小_java对象的大小

原文出处:http://www.open-open.com/lib/view/open1423111722764.html

最近在读《深入理解Java虚拟机》,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存?

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;

}

/**

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

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

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

*

* @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 visited = new HashSet();

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;

}

/**

* 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);

}

}

大家可以用这个代码边看边验证,注意的是,运行这个程序需要通过javaagent注入Instrumentation,具体可以看原博客。我今天主要是总结下手动计算Java对象占用字节数的基本规则,做为基本的技能必须get√,希望能帮到和我一样的Java菜鸟。

在介绍之前,简单回顾下,Java对象的内存布局:对象头(Header),实例数据(Instance Data)和对齐填充(Padding),详细的可以看我的读书笔记。另外:不同的环境结果可能有差异,我所在的环境是HotSpot虚拟机,64位Windwos。

下面进入正文:

对象头

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

a7d0265d8c2e29c21818b7b57c54ef81.png

5dbc5a64b4932b52dca8a6668ccc8d91.png

实例数据

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

Primitive Type

Memory Required(bytes)

boolean

1

byte

1

short

2

char

2

int

4

float

4

long

8

double

8

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;

}

A对象占用内存情况:

关闭指针压缩: 16+4=20不是8的倍数,所以+padding/4=24

0671d74ba2e104629f05734377fa62a8.png

开启指针压缩: 12+4=16已经是8的倍数了,不需要再padding。

b4a851e57cadb8f87f5a70b157cc8c52.png

2) 对reference类型的影响

64位机器上reference类型占用8个字节,开启指针压缩后占用4个字节。

static class B2 {

int b2a;

Integer b2b;

}

B2对象占用内存情况:

关闭指针压缩: 16+4+8=28不是8的倍数,所以+padding(=4)=32

dcba0319d5ae2658ae2cce5a1292262f.png

开启指针压缩: 12+4+4=20不是8的倍数,所以+padding/4=24

49d633b6afdc6309db806b666869393c.png

数组对象

64位机器上,数组对象的对象头占用24个字节,启用压缩之后占用16个字节。之所以比普通对象占用内存多是因为需要额外的空间存储数组的长度。

先考虑下new Integer[0]占用的内存大小,长度为0,即是对象头的大小:

未开启压缩:24bytes

f2e92e138b37888f67eaae0e68c2ce62.png

开启压缩后:16bytes

6dac7e431befccaac72e006373121750.png

接着计算new Integer[1],new Integer[2],new Integer[3]和new Integer[4]就很容易了:

未开启压缩:

e68d8df355a4edc7d27a133391b21204.png

开启压缩:

89db8cf9eabaee96650522795dcd08c3.png

拿new Integer[3]来具体解释下:

未开启压缩:24(对象头)+8*3=48,不需要padding;

开启压缩:16(对象头)+3*4=28,+padding/4=32,其他依次类推。

自定义类的数组也是一样的,比如:

static class B3 {

int a;

Integer b;

}

new B3[3]占用的内存大小:

未开启压缩:48

开启压缩后:32

复合对象

计算复合对象占用内存的大小其实就是运用上面几条规则,只是麻烦点。

1)对象本身的大小

直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数 组引用本身占用空间大小; 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小。

static class B {

int a;

int b;

}

static class C {

int ba;

B[] as = new B[3];

C() {

for (int i = 0; i < as.length; i++) {

as[i] = new B();

}

}

}

未开启压缩:16(对象头)+4(ba)+8(as引用的大小)+padding/4=32

开启压缩:12+4+4+padding/4=24

2)当前对象占用的空间总大小

递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小。

递归计算复合对象占用的内存的时候需要注意的是:对齐填充是以每个对象为单位进行的,看下面这个图就很容易明白。

276ff892e846d10ca7d246faf10faea5.png

现在我们来手动计算下C对象占用的全部内存是多少,主要是三部分构成:C对象本身的大小+数组对象的大小+B对象的大小。

未开启压缩:

(16 + 4 + 8+4(padding)) + (24+ 8*3) +(16+8)*3 = 152bytes

开启压缩:

(12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(数组对象padding)) + (12+8+4(B对象padding))*3= 128bytes

大家有兴趣的可以试试。

实际工作中真正需要手动计算对象大小的场景应该很少,但是个人觉得做为基础知识每个Java开发人员都应该了解,另外:对自己写的代码大概占用多少内存,内存中是怎么布局的应该有一个直觉性的认识。

转自http://www.open-open.com/lib/view/open1423111722764.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值