我有一个应用程序,读取一个CSV文件与成堆的数据行。我根据数据类型向用户提供了行数的摘要,但我想确保不会读入太多行数据而导致OutOfMemoryErrors。每一行转换成一个对象。有没有一种简单的方法以编程方式找出该对象的大小?是否有一个引用定义了一个VM的基本类型和对象引用有多大?

现在,我有代码说读取多达32,000行,但我还希望有代码说读取尽可能多的行,直到我使用了32MB的内存。也许这是另一个问题,但我还是想知道。


当前回答

我的答案是基于Nick提供的代码。该代码测量被序列化对象占用的字节总数。因此,这实际上衡量的是序列化的东西+普通对象的内存占用(只要序列化,例如int,你会看到序列化的字节总数不是4)。所以,如果你想获得对象使用的原始字节数,你需要修改一下代码。像这样:

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectSizeCalculator {
    private Object getFirstObjectReference(Object o) {
        String objectType = o.getClass().getTypeName();

        if (objectType.substring(objectType.length()-2).equals("[]")) {
            try {
                if (objectType.equals("java.lang.Object[]"))
                    return ((Object[])o)[0];
                else if (objectType.equals("int[]"))
                    return ((int[])o)[0];
                else
                    throw new RuntimeException("Not Implemented !");
            } catch (IndexOutOfBoundsException e) {
                return null;
            }
        }

        return o;
    } 

    public int getObjectSizeInBytes(Object o) {
        final String STRING_JAVA_TYPE_NAME = "java.lang.String";

        if (o == null)
            return 0;

        String objectType = o.getClass().getTypeName();
        boolean isArray = objectType.substring(objectType.length()-2).equals("[]");

        Object objRef = getFirstObjectReference(o);
        if (objRef != null && !(objRef instanceof Serializable))
            throw new RuntimeException("Object must be serializable for measuring it's memory footprint using this method !");

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(o);
            oos.close();
            byte[] bytes = baos.toByteArray();

            for (int i = bytes.length - 1, j = 0; i != 0; i--, j++) {
                if (objectType != STRING_JAVA_TYPE_NAME) {
                    if (bytes[i] == 112)
                        if (isArray)
                            return j - 4;
                        else
                            return j;
                } else {
                    if (bytes[i] == 0)
                        return j - 1;
                }
            }
        } catch (Exception e) {
            return -1;
        }

        return -1;
    }    

}

我已经用基本类型String和一些普通类测试了这个解决方案。可能也有不包括在内的情况。

更新:示例修改为支持数组对象的内存占用计算。

其他回答

首先,“对象的大小”在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大小初始化为一个合理的值,代码将会更有效。

您可以生成一个堆转储(例如,使用jmap),然后分析输出以查找对象大小。这是一种离线解决方案,但是您可以检查浅尺寸和深尺寸等。

我的答案是基于Nick提供的代码。该代码测量被序列化对象占用的字节总数。因此,这实际上衡量的是序列化的东西+普通对象的内存占用(只要序列化,例如int,你会看到序列化的字节总数不是4)。所以,如果你想获得对象使用的原始字节数,你需要修改一下代码。像这样:

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectSizeCalculator {
    private Object getFirstObjectReference(Object o) {
        String objectType = o.getClass().getTypeName();

        if (objectType.substring(objectType.length()-2).equals("[]")) {
            try {
                if (objectType.equals("java.lang.Object[]"))
                    return ((Object[])o)[0];
                else if (objectType.equals("int[]"))
                    return ((int[])o)[0];
                else
                    throw new RuntimeException("Not Implemented !");
            } catch (IndexOutOfBoundsException e) {
                return null;
            }
        }

        return o;
    } 

    public int getObjectSizeInBytes(Object o) {
        final String STRING_JAVA_TYPE_NAME = "java.lang.String";

        if (o == null)
            return 0;

        String objectType = o.getClass().getTypeName();
        boolean isArray = objectType.substring(objectType.length()-2).equals("[]");

        Object objRef = getFirstObjectReference(o);
        if (objRef != null && !(objRef instanceof Serializable))
            throw new RuntimeException("Object must be serializable for measuring it's memory footprint using this method !");

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(o);
            oos.close();
            byte[] bytes = baos.toByteArray();

            for (int i = bytes.length - 1, j = 0; i != 0; i--, j++) {
                if (objectType != STRING_JAVA_TYPE_NAME) {
                    if (bytes[i] == 112)
                        if (isArray)
                            return j - 4;
                        else
                            return j;
                } else {
                    if (bytes[i] == 0)
                        return j - 1;
                }
            }
        } catch (Exception e) {
            return -1;
        }

        return -1;
    }    

}

我已经用基本类型String和一些普通类测试了这个解决方案。可能也有不包括在内的情况。

更新:示例修改为支持数组对象的内存占用计算。

使用java visual VM即可。

它具有分析和调试内存问题所需的一切。

它还有一个OQL(对象查询语言)控制台,可以让你做很多有用的事情,其中之一是sizeof(o)

当我在Twitter工作时,我写了一个计算深度对象大小的实用程序。它考虑了不同的内存模型(32位,压缩oops, 64位),填充,子类填充,在循环数据结构和数组上正确工作。你可以编译这个。java文件;它没有外部依赖:

https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/objectsize/ObjectSizeCalculator.java