一个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

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>

     *

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

    

}

大家可以用这个代码边看边验证,注意的是,运行这个程序需要通过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

static class A {

        int a;

    }

A对象占用内存情况:

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

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

2) 对reference类型的影响

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


1

2

3

4

static class B2 {

        int b2a;

        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

static class B3 {

        int a;

        Integer b;

    }

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

未开启压缩:48

开启压缩后:32

复合对象

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

1)对象本身的大小

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


1

2

3

4

5

6

7

8

9

10

11

12

13

14

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)当前对象占用的空间总大小

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

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

现在我们来手动计算下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.importnew.com/14948.html

 

时间: 2024-10-28 15:01:33

一个Java对象到底占多大内存?(转)的相关文章

计算一个Java对象占用字节数的方法_java

本文实例讲述了如何计算(或者说,估算)一个Java对象占用的内存数量的方法.分享给大家供大家参考.具体分析如下: 通常,我们谈论的堆内存使用的前提是以"一般情况"为背景的.不包括下面两种情形:   某些情况下,JVM根本就没有把Object放入堆中.例如:原则上讲,一个小的thread-local对象存在于栈中,而不是在堆中. 被Object占用内存的大小依赖于Object的当前状态.例如:Object的同步锁是否生效,或者,Object是否正在被回收. 我们先来看看在堆中单个的Obj

内存在300m左右-WebView 关于显示图片轮播的网页,占很大内存,有什么方法优化

问题描述 WebView 关于显示图片轮播的网页,占很大内存,有什么方法优化 显示轮播图片40张,内存在300M左右,如何解决?同时图片切换也很不流畅 解决方案 ViewPager 显示图片,轮播 解决方案二: 以最省内存的方式读取本地资源的图片 Android 中加载图片的颜色模式有四种,分别是:ALPHA_8:每个像素占用 1byte 内存.ARGB_4444:每个像素占用 2byte 内存.ARGB_8888:每个像素占用 4byte 内存.RGB_565:每个像素占用 2byte 内存.

游戏-这个一个bat文件,用来启动一个java项目时报错。大神帮忙解读一下是什么意思。

问题描述 这个一个bat文件,用来启动一个java项目时报错.大神帮忙解读一下是什么意思. 解决方案 解释下你这个$ARGS是用来做什么的. 解决方案二: 目测args是arguments的缩写,代表一个参数,而不是实际的命令.

剖析一个java对象初始化顺序问题

今天我在Dzone阅读了一篇关于java对象实例初始化顺序的有趣文章.说它有趣,是因为作者使用了一种并不太推荐的编码风格,只有用这种编码风格才能触发这个极为少见的 Java object initialization order 问题. 其实java对象初始化顺序算是一个比较基础的java知识点.但是网上的文章多半描述不清,使用上一不小心就容易出问题. 所以在本文中,我想结合JLS和自己的理解,举例剖析问题的所在. OK,我们先来看个模仿Dzone作者原意的简单例子: [java] packag

Java对象大小内幕浅析

 最近突发奇想,忽然对Java对象的内存大小感兴趣,去网上搜集了一些资料,并且做一下整理,希望能够各位帮助.  如果:你能算出new String("abc")这个对象在JVM中占用内存大小(64位JDK7中压缩大小48B,未压缩大小64B), 那么看到这里就可以结束了~  Java对象的内存布局:对象头(Header),实例数据(Instance Data)和对齐填充(Padding).  虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如hashCode.GC分

请教大神们一个java包装类的问题

问题描述 请教大神们一个java包装类的问题 请教大神们一个java包装类的问题, String str = "abc"; String str2 = "abc"; System.out.println(str == str2); Integer i = Integer.valueOf(1); Integer i2 = Integer.valueOf(1); System.out.println(i ==i2); 我知道str和str2是存放在字符串常量池中的,所以

如何精确地测量java对象的大小-底层instrument API

关于java对象的大小测量,网上有很多例子,大多数是申请一个对象后开始做GC,后对比前后的大小,不过这样,虽然说这样测量对象的大小是可行的,不过未必是完全准确的,因为过程中包含对象本身的开销,也许你运气好,正好能碰上,差不多,不过这种测试往往显得十分的笨重,因为要写一堆代码才能测试一点点东西,而且只能在本地测试玩玩,要真正测试实际的系统的对象大小这样可就不行了,本文说说java一些比较偏底层的知识,如何测量对象大小,java其实也是有提供方法的.注意:本文的内容仅仅针对于Hotspot VM,如

JVM源码分析之Java对象的创建过程

本文将基于HotSpot实现对Java对象的创建过程进行深入分析. 定义两个简单的类AAA和BBB 通过"javap -c AAA"`查看编译之后的字节码,具体如下: Java中的new关键字对应jvm中的new指令,定义在InterpreterRuntime类中,实现如下: new指令的实现过程: 1.其中pool是AAA的constant pool,此时AAA的class已经加载到虚拟机中,new指令后面的#2表示BBB类全限定名的符号引用在constant pool的位置: 2.

传递和使用Java对象

在前例中,我们将一个字串传递给固有方法.事实上,亦可将自己创建的Java对象传递给固有方法. 在我们的固有方法内部,可访问已收到的那些对象的字段及方法. 为传递对象,声明固有方法时要采用原始的Java语法.如下例所示,MyJavaClass有一个public(公共)字段,以及一个public方法.UseObjects类声明了一个固有方法,用于接收MyJavaClass类的一个对象.为调查固有方法是否能控制自己的自变量,我们设置了自变量的public字段,调用固有方法,然后打印出public字段的