在Java中,确定对象大小的最佳方法是什么?
例如,假设我有一个应用程序,可以读取带有大量数据行的CSV文件。我根据数据类型为用户提供了行数的摘要,但是我想确保我不会读入过多的数据行并导致OutOfMemoryError
。每行转换为一个对象。有没有一种简便的方法以编程方式找出该对象的大小?是否有参考定义了VM的原始类型和对象参考有多大?
现在,我的代码可以读取多达32,000行,但是我还想让代码读取尽可能多的行,直到使用32MB内存为止。也许这是一个不同的问题,但我仍然想知道。
解决方案
回答
我怀疑我们是否希望以编程方式执行此操作,除非我们只想执行一次并将其存储以备将来使用。这是一件昂贵的事情。 Java中没有sizeof()运算符,即使有,它也只会计算对其他对象的引用成本和原语的大小。
一种方法是将事物序列化为文件,然后查看文件的大小,如下所示:
Serializable myObject; ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("obj.ser")); oos.write (myObject); oos.close ();
当然,这假设每个对象都是不同的,并且不包含对其他任何对象的非临时引用。
另一种策略是获取每个对象,并通过反射检查其成员,然后累加大小(布尔值和字节= 1个字节,短字符和char = 2个字节,等等),沿着成员资格层次结构逐步进行。但这既繁琐又昂贵,并且最终会执行序列化策略将要执行的相同操作。
回答
如果我们要的是方法调用,那么就没有方法调用。经过一点研究,我想我们可以自己编写。特定实例具有固定的大小,该大小是从引用和原始值的数量加上实例簿记数据得出的。我们只需遍历对象图即可。行类型变化越少,越容易。
如果这太慢或者麻烦太多了,那么总会有一些老式的行计数经验法则。
回答
如果我们只想知道JVM中正在使用多少内存以及多少空闲空间,可以尝试执行以下操作:
// Get current size of heap in bytes long heapSize = Runtime.getRuntime().totalMemory(); // Get maximum size of heap in bytes. The heap cannot grow beyond this size. // Any attempt will result in an OutOfMemoryException. long heapMaxSize = Runtime.getRuntime().maxMemory(); // Get amount of free memory within the heap in bytes. This size will increase // after garbage collection and decrease as new objects are created. long heapFreeSize = Runtime.getRuntime().freeMemory();
编辑:我认为这可能会有所帮助,因为问题作者还说他希望逻辑可以处理"在我使用32MB内存之前,读取尽可能多的行"。
回答
我们必须使用反射来走动对象。操作时请小心:
- 仅分配对象在JVM中会有一些开销。该数量因JVM而异,因此我们可以将此值作为参数。至少使其为常数(8个字节?)并适用于分配的任何内容。
- 仅仅因为" byte"在理论上是1字节并不意味着它仅占用一个内存。
- 对象引用中将存在循环,因此我们需要使用对象相等作为比较器来保留" HashMap"或者类似的东西,以消除无限循环。
@jodonnell:我喜欢解决方案的简单性,但是许多对象不是可序列化的(因此这将引发异常),字段可以是瞬态的,并且对象可以覆盖标准方法。
回答
我们必须使用工具对其进行测量,或者手动进行估算,这取决于我们所使用的JVM。
每个对象有一些固定的开销。它是特定于JVM的,但我通常估计40个字节。然后,我们必须查看类的成员。在32位(64位)JVM中,对象引用是4(8)字节。基本类型为:
- 布尔值和字节:1个字节
- char和short:2个字节
- int和float:4个字节
- 长整型:8个字节
数组遵循相同的规则;也就是说,它是一个对象引用,因此在对象中占用4(或者8)个字节,然后将其长度乘以其元素的大小。
尝试通过调用Runtime.freeMemory()来以编程方式完成该操作并不会给我们带来太多的准确性,因为对垃圾收集器进行了异步调用等。使用-Xrunhprof或者其他工具对堆进行概要分析将使我们最准确结果。
回答
首先,"对象的大小"在Java中不是一个定义明确的概念。我们可以指对象本身,仅包含其成员,对象及其引用的所有对象(参考图)。我们可以指内存中的大小或者磁盘上的大小。并且允许JVM优化诸如字符串之类的东西。
因此,唯一正确的方法是使用一个良好的探查器(我使用YourKit)来询问JVM,这可能不是我们想要的。
但是,从上面的描述中,听起来每一行都是独立的,并且没有大的依赖关系树,因此序列化方法在大多数JVM上可能是一个很好的近似值。最简单的方法如下:
Serializable ser; ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(ser); oos.close(); return baos.size();
请记住,如果对象具有公共引用,这将无法给出正确的结果,并且序列化的大小并不总是与内存中的大小匹配,但这是一个很好的近似值。如果将ByteArrayOutputStream的大小初始化为一个合理的值,则代码将更有效率。
回答
几年前,Javaworld发表了一篇有关确定复合对象和可能嵌套的Java对象大小的文章,它们基本上遍历了在Java中创建sizeof()实现的过程。该方法基本上以其他工作为基础,在该工作中,人们通过实验确定了基元和典型Java对象的大小,然后将该知识应用于递归地遍历对象图以计算总大小的方法。
仅仅因为类的幕后发生的事情,它总是比本地C实现的准确性要差一些,但这应该是一个很好的指标。
或者,一个SourceForge项目,适当地称为sizeof,它为Java5库提供了sizeof()实现。
P.S.不要使用序列化方法,序列化对象的大小与其在活动时消耗的内存量之间没有关联。
回答
我们可以使用java.lang.instrument包
编译该类并将其放在JAR中:
import java.lang.instrument.Instrumentation; public class ObjectSizeFetcher { private static Instrumentation instrumentation; public static void premain(String args, Instrumentation inst) { instrumentation = inst; } public static long getObjectSize(Object o) { return instrumentation.getObjectSize(o); } }
将以下内容添加到MANIFEST.MF
中:
Premain-Class: ObjectSizeFetcher
使用getObjectSize:
public class C { private int x; private int y; public static void main(String [] args) { System.out.println(ObjectSizeFetcher.getObjectSize(new C())); } }
调用:
java -javaagent:ObjectSizeFetcherAgent.jar C