[Java拾遗]Java对象大小探究

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

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



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

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

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

    我们的目标类像这样:
 
Java代码   收藏代码
  1. public class BytesDemo {  
  2.   
  3.     long a;  
  4.     static int kk;  
  5.     int ano;  
  6.   
  7.     public static void main(String[] args) {  
  8.         BytesDemo demo = new BytesDemo();  
  9.         System.out.println("Object size : " + MemoryUtil.memoryUsageOf(demo));  
  10.     }  
  11.   
  12. }  


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

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

    如果我们的目标类变成这样
 
Java代码   收藏代码
  1. public class BytesDemo {  
  2.     long a;  
  3.     static int kk;  
  4.     int ano;  
  5.   
  6.     private List<String> cache;  
  7.   
  8.     public static void main(String[] args) {  
  9.         BytesDemo demo = new BytesDemo();  
  10.   
  11.         demo.cache = new ArrayList<String>();  
  12.         demo.cache.add("firstKey");  
  13.   
  14.         System.out.println("Object size : " + MemoryUtil.deepMemoryUsageOf(demo));  
  15.     }  
  16. }  


    请注意,这里新增加了了一个属性,而且对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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值