new Java对象占用内存分析

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

在网上搜到了一篇博客讲的非常好:http://yueyemaitian.iteye.com/blog/2033046,里面提供的这个类也非常实用:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

importjava.lang.instrument.Instrumentation;

importjava.lang.reflect.Array;

importjava.lang.reflect.Field;

importjava.lang.reflect.Modifier;

importjava.util.ArrayDeque;

importjava.util.Deque;

importjava.util.HashSet;

importjava.util.Set;

/**

* 对象占用字节大小工具类

*

*
@author tianmai.fh

* @date 2014-03-18 11:29

*/

publicclassSizeOfObject {

staticInstrumentation inst;

publicstaticvoidpremain(String args, Instrumentation instP) {

inst = instP;

}

/**

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

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

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

*

*
@param obj

*
@return

*/

publicstaticlongsizeOf(Object obj) {

returninst.getObjectSize(obj);

}

/**

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

*

*
@param objP

* @return

* @throws IllegalAccessException

*/

publicstaticlongfullSizeOf(Object objP)throwsIllegalAccessException {

Set<Object> visited =newHashSet<Object>();

Deque<Object> toBeQueue =newArrayDeque<Object>();

toBeQueue.add(objP);

longsize = 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(inti =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();

}

}

}

returnsize;

}

/**

* String.intern的对象不计;计算过的不计,也避免死循环

*

* @param visited

* @param obj

* @return

*/

staticbooleanskipObject(Set<Object> visited, Object obj) {

if(objinstanceofString && obj == ((String) obj).intern()) {

returntrue;

}

returnvisited.contains(obj);

}

}

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

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

下面进入正文:

对象头

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

实例数据

原生类型(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位机器)。


1

2

3

staticclassA {

inta;

}

A对象占用内存情况:

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

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

2) 对reference类型的影响

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


1

2

3

4

staticclassB2 {

intb2a;

Integer b2b;

}

B2对象占用内存情况:

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

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

数组对象

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

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

未开启压缩:24bytes

开启压缩后:16bytes

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

未开启压缩:

开启压缩:

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

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

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

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


1

2

3

4

staticclassB3 {

inta;

Integer b;

}

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

未开启压缩:48

开启压缩后:32

复合对象

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

1)对象本身的大小

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


1

2

3

4

5

6

7

8

9

10

11

12

13

14

staticclassB {

inta;

intb;

}

staticclassC {

intba;

B[] as =newB[3];

C() {

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

as[i] =newB();

}

}

}

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

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

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

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

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

现在我们来手动计算下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

时间: 2024-12-29 10:16:56

new Java对象占用内存分析的相关文章

java对象占用内存大小计算方式

案例一: User public class User { } UserSizeTest public class UserSizeTest { static final Runtime runTime=Runtime.getRuntime(); public static void main(String[] args) { final int count = 100000; User[] us=new User[count]; long heap1 = 0; for (int i = -1;

java 对象占用内存

String 方法用于文本分析及大量字符串处理时会对内存性能造成一些影响.可能导致内存占用太大甚至OOM. 一.先介绍一下String对象的内存占用 一般而言,Java 对象在虚拟机的结构如下:•对象头(object header):8 个字节(保存对象的 class 信息.ID.在虚拟机中的状态)•Java 原始类型数据:如 int, float, char 等类型的数据•引用(reference):4 个字节•填充符(padding) String定义: JDK6:private final

java 对象占用内存查看 以及JVM级别 方法修改等

public interface Instrumentation 此类提供检测 Java 编程语言代码所需的服务.检测是向方法中添加字节码,以搜集各种工具所使用的数据.由于更改完全是进行添加,所以这些工具不修改应用程序的状态或行为.这种无害工具的例子包括镜像代理.分析器.覆盖分析器和事件记录器. http://www.iteye.com/problems/31779 http://www.jroller.com/maxim/entry/again_about_determining_size_o

java对象的内存布局(一):计算java对象占用的内存空间以及java object layout工具的使用

最近在学习java对象内存布局方面的一些知识,主要是想知道一个java对象到底占用多少内存空间,以及java对象在内存中到底是什么样子的.c/c++中的sizeof运算符能够方便地告诉我们一个变量占用的内存空间,但是在java中却没有直接提供这种机制.如果想获取java对象占用的内存大小,可以利用java的Instrumentation机制.java.lang.instrument.Instrumentation这个接口提供了getObjectSize(Object objectToSize),

JAVA内存使用--如何计算一个Java对象占用的字节数

本文中,我们讨论一个问题:如何计算(或者说,估算)一个Java对象占用的内存数量? 通常,我们谈论的堆内存使用的前提是以"一般情况"为背景的.不包括下面两种情形: 某些情况下,JVM根本就没有把Object放入堆中.例如:原则上讲,一个小的thread-local对象存在于栈中,而不是在堆中. 被Object占用内存的大小依赖于Object的当前状态.例如:Object的同步锁是否生效,或者,Object是否正在被回收. 我们先来看看在堆中单个的Object长什么样子 在堆中,每个对象

记一次对java对象在内存中的分析

java 对象 占内存大小 计算方式 及 常用类型的占用 HotSpot的对齐方式为8字节对齐 ----计算公式:(对象头 + 实例数据 + padding) % 8等于0且0 <= padding < 8 Hotspot 机 中 普通对象32位 对象头 占 8个字节 引用类型 占 4字节64位 对象头 占 16个字节 引用类型 占 8字节 64位中 空对象数组 对象头 占 24 增加一个长度 增加 一个引用类型的长度 64位中是 8空的基本数据类型数组 对象头 占 24 增加一个长度 增加一

Java对象的内存布局

Java对象的内存布局:对象头(Header),实例数据(Instance Data),对齐填充(Padding):另外:不同的环境结果可能有差异,我所在的环境是HotSpot虚拟机,64位Windows. 对象头 对象头在32位系统上占用8bytes,64位系统上占用16bytes. System.out.println("sizeOf(new Object()) = " + sizeOf(new Object())); sizeOf(new Object()) = 16 实例数据

vector动态二维数组(容器的容器)占用内存分析

之前在这里写过一篇"C++中的动态二维数组".在C++中没有动态二维(多维)数组.但是根据原理我们可以自己创建. 在看过STL的vector源代码后"<STL源码剖析>---stl_vector.h阅读笔记"后,想到可以用容器的容器来做二维数组. 创建一个2x4的二维数组.想到的办法是:先创建一个容器的容器,外层大小的2(2行),然后里面容器小大为4(4列). int row=2,col=4; vector<vector<int> &g

Java对象的内存布局以及对象的访问定位

先来看看Java对象在内存中的布局 一 Java对象的内存布局 在HotSpot虚拟机中,对象在内存中的布局分为3个区域 对象头(Header) Mark Word(在32bit和64bit虚拟机上长度分别为32bit和64bit)存储对象自身的运行时数据,包括哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时 间戳等 类型指针 即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.但是并不是所有类型虚拟机实现都必须在对象数据上保留类型指针,如果对象是一