您的位置:首页 > 编程语言 > Java开发

Java拾遗]Java对象大小探究

2011-12-26 14:21 176 查看
 Java拾遗]Java对象大小探究
博客分类:
javaoutofmemoryheap

    平时我们不会关心生成的对象到底在JVM中占据多少内存,当发生像OutOfMemory或JVM内存异常增加或减少时才会花精力研究到底发生了什么事情。如果当我们发现有些对象确实很大,但超过我们预期时,我们就该关心下我们所期望创建的每个对象大致会在JVM中占用多少内存呢。这节我会试着以一个更循序诱导的方法来描述,希望可以说的更明白,下面开始:

当遇到OutOfMemory时我们该怎么办?

    一般这个时候,作为我们程序员,心都会焦了,急于想知道到底是哪些对象引起内存不足。我们要做的就是dump heap,然后抓出来分析。这里有一张前些时间我也遇到的OOM问题截图:



    从图上可以抓到“凶手”,剩下的事情就简单多了。

    有个问题:如果现在没有遇到OOM,那我们怎样对某个对象大小有个理性评估呢?

使用Java Instrumentaion来评估每个对象的大小

    Java Instrumentation机制为程序运行提供agent,它的附带功能就是获得已经初始化并准备运行的那个对象大小。这里我采用了javamex.com提供的一个包装util类,方便我们查看对象大小。Jar文件在附件中

    我们的目标类像这样:

Java代码

 




public class BytesDemo {   
  
    long a;   
    static int kk;   
    int ano;   
  
    public static void main(String[] args) {   
        BytesDemo demo = new BytesDemo();   
        System.out.println("Object size : " + MemoryUtil.memoryUsageOf(demo));   
    }   
  
}  

public class BytesDemo {

long a;
static int kk;
int ano;

public static void main(String[] args) {
BytesDemo demo = new BytesDemo();
System.out.println("Object size : " + MemoryUtil.memoryUsageOf(demo));
}

}


    这个程序在运行时,它会显示多少呢?



    (这里可以看到,我的运行是使用了classmexer.jar,并且运行在class文件所在的编译目录,如果你不能获得这样的输出结果,那么请检查classmexer.jar是否放对位置和是否在class文件所在目录上执行java)

    如果我们的目标类变成这样

Java代码

 




public class BytesDemo {   
    long a;   
    static int kk;   
    int ano;   
  
    private List<String> cache;   
  
    public static void main(String[] args) {   
        BytesDemo demo = new BytesDemo();   
  
        demo.cache = new ArrayList<String>();   
        demo.cache.add("firstKey");   
  
        System.out.println("Object size : " + MemoryUtil.deepMemoryUsageOf(demo));   
    }   
}  

public class BytesDemo {
long a;
static int kk;
int ano;

private List<String> cache;

public static void main(String[] args) {
BytesDemo demo = new BytesDemo();

demo.cache = new ArrayList<String>();
demo.cache.add("firstKey");

System.out.println("Object size : " + MemoryUtil.deepMemoryUsageOf(demo));
}
}


    请注意,这里新增加了了一个属性,而且对MemoryUtil调用的方法也不一样,它的结果是:



对象与对象之间的关系

    对象都存储于JVM堆中,对象与对象之间通过引用链接-直接指向目标对象的物理位置。这里有一张图,是我自己对堆内对象存储情况的理解,事实可能有些不一致,仅仅是让我们有直观印象



    每个对象的物理存储可以分为两部分:Header及该对象的所有对象属性值(不包括static属性)。Hotspot VM限定每个对象header是2个word,word是JVM内部的存储最小单位,当前Hotspot定义的word大小是4字节,所以header共是8个字节。Header中应当包含着本对象的hashCode,对象锁及与GC相关的生存周期信息等。对象属性分为两部分:基本类型属性与对象引用。Java事先定义了所有基本类型的占用位数,如下表:



    基本类型对象属性依上表占用着堆内存,而每个对其它对象的引用是规定占用一个word,也就是4个字节。像上面第二个目标类中新增加一个对象引用cache,那么这个引用属性就只占用4个字节。

    正常的对象引用也有两种:普通对象与数组。数组也是正常对象,只不过,它除了header外还有4个字节表示当前数组的长度是多少,那么我们也可以认为数组对象的header长度就是12个字节了。

    在这里要特别强调的是如果某个普通对象就包含一个byte属性,那么它的对象大小应该是9个字节。而JVM为了malloc与gc方便,指定分配的每个对象都需要是8字节的整数倍,那么对象大小就不再是9个字节,而应该是16个字节。

    在了解上面的这些对象计算规则后,也请大家计算上面两个目标类的对象大小是否符合预期。

    从最上面的一张图上看到了两个名词: shallow size 和 retained size。Shallow表示本对象自身的大小是多少。本对象可能会直接或间接引用其它很多对象,如果被引用的对象仅仅被本对象所引用,那么当本对象无用被GC时,那么本对象所引用的对象也会被GC。Retained就表示如果当本对象被GC时能相关地减少的内存量。这里有个参考资料说的十分详细。

类的继承关系对对象大小的影响

    类如果有继承关系,那么按Java的定义,如果某个子类想要被初始化,就得先初始化自己的父类,这样子类对象其实也包含着所有其父类的非static属性。这种关系同样作用于像内部类这种结构里。

参考资料:

1.
Object memory usage

2.
Object memory structure
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息