如何在Java中估算对象的序列化大小,而不实际进行序列化?

16
为了提高集群中的消息传递效率,了解消息大小(应该优先处理本地还是远程)非常重要。
我可以找到一些基于Java工具的对象内存大小估算框架。我测试了classmexer和sourceforge SizeOf,但它们都无法接近序列化大小。
在小型测试用例中,SizeOf偏差约为10%,速度比序列化快10倍。(仍然有transient会完全破坏估算,例如ArrayList是transient但作为Array进行序列化,因此很难修补SizeOf。但我可以接受这个问题)
另一方面,速度快10倍且误差为10%似乎并不是很好。您有什么更好的想法吗?
更新:我还测试了ObjectSize(http://sourceforge.net/projects/objectsize-java)。结果似乎只适用于非继承对象 :(

1
消息是如何交换的?我认为你的问题没有明确说明消息是否使用Java序列化进行序列化 - 请确认一下,这是这种情况吗? - matt b
抱歉,我忘了提到:这是Java序列化。 - Stefan K.
如果您可以对它们进行序列化,那么简单的答案是:https://dev59.com/OG865IYBdhLWcg3wM7q0。如果不能,您应该尝试 https://dev59.com/TnVD5IYBdhLWcg3wNIzc 中的每种方法来确定内存中表示大小,并选择误差较小的方法。 - Ciro Santilli OurBigBook.com
4个回答

5

其他答案中有很多好的观点,但缺少一点是序列化机制可能会缓存某些对象

例如,您将序列化一系列同一类别的对象A、B和C,这些对象在每个对象中都包含两个对象o1和o2。假设对象开销为100字节,对象看起来像:

Object shared = new Object();
Object shread2 = new Object();

A.o1 = new Object()
A.o2 = shared


B.o1 = shared2
B.o2 = shared


C.o1 = shared2
C.o2 = shared

为了简单起见,我们可以说通用对象需要50个字节来序列化,A的序列化大小为100(开销)+ 50(o1)+ 50(o2)= 200个字节。对于B和C,我们也可以进行类似的天真估计。然而,如果在重置被调用之前,所有三个对象都由同一个对象输出流序列化,那么您在流中看到的是A、o1和o2的序列化,然后是B和o1的序列化,但由于o2已经被序列化过了,所以只有一个对o2的引用。因此,假设一个对象引用占用16个字节,则B的大小现在为100(开销)+ 50(o1)+ 16(o2的引用)= 166个字节。因此,序列化所需的大小现在已经改变! 我们可以对C进行类似的计算,并获得132个字节,其中缓存了两个对象,因此所有三个对象的序列化大小都不同,最大值与最小值之间的差异约为33%。
因此,除非您每次都没有缓存地序列化整个对象,否则很难准确估计序列化对象所需的大小。

这是一个很好的观点。我忘了提到只有1个对象被序列化,然后流被重置(至少我希望如此,否则它将成为框架的问题)。你知道Java的序列化是否足够聪明,可以一次序列化相等的对象吗? 例如,用“new Long(10L)”替换您共享的示例?所有对象都将拥有自己的Long实例(不是==),但它们都是equal()的。 - Stefan K.

5
一个类在运行时所占的空间并不一定与它在内存中占用的大小有关。你提到的一个例子就是瞬态字段,其他的例子包括对象实现了Externalizable接口并自己处理序列化。
如果一个对象实现了Externalizable接口或提供了readObject()/writeObject()方法,那么最好将对象序列化到内存缓冲区中来找出其大小。这不会很快,但它会更准确。
如果一个对象使用默认的序列化方式,那么您可以修改SizeOf方法以考虑瞬态字段。
在对许多相同类型的对象进行序列化之后,您可以创建一个“序列化配置文件”,将序列化大小与SizeOf的运行时大小相关联。这将使您能够快速估算序列化大小(使用SizeOf),然后将其与运行时大小相关联,从而得出比SizeOf提供的更准确的结果。

好的观点。我必须记住(假设我有一个很好的估计),如果我估计Externalizable的子类,我应该回退到序列化来进行测量。 - Stefan K.
我刚刚意识到,即使不实现Externalizable接口也可以重写readObject()/writeObject()。所以我的“备选”策略现在变成了首选:)。也许我可以通过实现自己的outputstream仅收集大小来稍微提高性能。哎呀。 - Stefan K.

3

一个想法 - 你可以先将对象序列化为字节缓冲区,获取其长度,并决定是否将缓冲区内容发送到远程位置或进行本地处理(如果它取决于消息的大小)。

缺点 - 如果后来决定不使用缓冲区,则可能会浪费序列化时间。但是如果你估计需要序列化(因为在这种情况下你首先估计,然后再进行序列化),则可以避免浪费时间。


Java序列化的性能测量是使用ByteArrayOutputStream完成的。我也有同样的想法,但我假设只需要对每50个消息进行序列化(我正在使用actors)。因此,测量消息大小的性能影响非常显著。 - Stefan K.

2

无法精确快速地估计对象的序列化大小。例如,某些对象可能是Pi数字的缓存,在运行时仅给出所需长度即可构建自身。因此,它只会序列化“长度”属性的4个字节,而该对象可能使用数百兆字节的内存来存储该Pi数字。

我能想到的唯一解决方案是添加您自己的接口,具有方法int estimateSerializeSize()。对于每个实现此接口的对象,您需要调用此方法以获取正确的大小。如果某个对象没有实现它,则必须使用SizeOf。


说实话:我不明白你的意思。如果你的PiCache序列化为4个字节,那么其他“数百兆字节的内存”都是瞬态的。或者它们与对象一起序列化。我不想通过构造函数来估计对象的大小。你说得对,内存和序列化大小并不能直接比较。我猜测按照非瞬态对象图并总结对象的内存大小会比较接近。如此描述的序列化开销-请参考www.javaworld.com/community/node/2915- 可以忽略,以获得显著的性能提升。 - Stefan K.
也许PiCache示例不是很精确。我只是想找到一些例子,其中对象占用的内存大大超过它将序列化为什么。但是,对象经常实现自定义序列化,这使得差异达到了10%。就像PiCache示例一样-可能根本没有'length'属性,在构造函数中生成缓存,将其放入某个List<Integer>中,并仅序列化list.size()。这种自定义序列化是使您想要最小化的10%。而且没有自动预测此类序列化的方法。 - bezmax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接