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

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

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

[img]http://dl.iteye.com/upload/attachment/550909/7b2e0fb4-0469-3cd2-be44-0671d0fc5be2.jpg[/img]
[size=medium]
从图上可以抓到“凶手”,剩下的事情就简单多了。

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

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

我们的目标类像这样:[/size]
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));
}

}

[size=medium]
这个程序在运行时,它会显示多少呢?[/size]
[img]http://dl.iteye.com/upload/attachment/550916/492740c9-f8f9-3eff-b253-4878a5595757.jpg[/img]
[size=medium]
(这里可以看到,我的运行是使用了classmexer.jar,并且运行在class文件所在的编译目录,如果你不能获得这样的输出结果,那么请检查classmexer.jar是否放对位置和是否在class文件所在目录上执行java)

如果我们的目标类变成这样[/size]
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));
}
}

[size=medium]
请注意,这里新增加了了一个属性,而且对MemoryUtil调用的方法也不一样,它的结果是:[/size]
[img]http://dl.iteye.com/upload/attachment/550920/276f5fe0-bb2b-3c28-bb27-224a59ea38f5.jpg[/img]

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

[img]http://dl.iteye.com/upload/attachment/550927/14d42995-ec71-3e2a-bc60-0d392b93da14.jpg[/img]
[size=medium]
每个对象的物理存储可以分为两部分:Header及该对象的所有对象属性值(不包括static属性)。Hotspot VM限定每个对象header是2个word,word是JVM内部的存储最小单位,当前Hotspot定义的word大小是4字节,所以header共是8个字节。Header中应当包含着本对象的hashCode,对象锁及与GC相关的生存周期信息等。对象属性分为两部分:基本类型属性与对象引用。Java事先定义了所有基本类型的占用位数,如下表:[/size]
[img]http://dl.iteye.com/upload/attachment/550911/0e224bca-8034-332b-bf19-cf67b597c92f.jpg[/img]
[size=medium]
基本类型对象属性依上表占用着堆内存,而每个对其它对象的引用是规定占用一个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时能相关地减少的内存量。这里有个[url]参考资料[/url]说的十分详细。

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


参考资料:
1. [url=http://www.javamex.com/tutorials/memory/object_memory_usage.shtml]Object memory usage[/url]
2. [url=http://www.codeinstructions.com/2008/12/java-objects-memory-structure.html]Object memory structure[/url][/size]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值