在Java中,确定对象大小的最佳方法是什么?

时间:2020-03-05 18:50:11  来源:igfitidea点击:

例如,假设我有一个应用程序,可以读取带有大量数据行的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