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

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


当前回答

我偶然发现了一个java类 "jdk.nashorn.internal.ir.debug. objectsizecalculator ",已经在jdk中, 这很容易使用,似乎对确定物体的大小非常有用。

System.out.println(ObjectSizeCalculator.getObjectSize(new gnu.trove.map.hash.TObjectIntHashMap<String>(12000, 0.6f, -1)));
System.out.println(ObjectSizeCalculator.getObjectSize(new HashMap<String, Integer>(100000)));
System.out.println(ObjectSizeCalculator.getObjectSize(3));
System.out.println(ObjectSizeCalculator.getObjectSize(new int[]{1, 2, 3, 4, 5, 6, 7 }));
System.out.println(ObjectSizeCalculator.getObjectSize(new int[100]));

结果:

164192
48
16
48
416

其他回答

我的答案是基于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和一些普通类测试了这个解决方案。可能也有不包括在内的情况。

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

不需要干扰插装等,如果你不需要知道一个对象的确切字节大小,你可以使用以下方法:

System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

do your job here

System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

通过这种方式,您可以读取之前和之后使用的内存,并在获得使用的内存之前调用GC,将“噪声”降低到几乎为0。

为了得到更可靠的结果,您可以运行作业n次,然后将使用的内存除以n,得到一次运行占用的内存。甚至,你可以把整个过程运行更多次,得到一个平均值。

对于JSONObject,下面的代码可以帮助您。

`JSONObject.toString().getBytes("UTF-8").length`

返回以字节为单位的大小

我通过将JSONArray对象写入文件来检查它。它给出了对象的大小。

我正在寻找一个对象大小的运行时计算,满足以下要求:

在运行时可用,不需要包括插装。 使用Java 9+,无需访问Unsafe。 仅基于类。不是考虑字符串长度,数组长度等的深度sizeOf。

以下内容基于java专家的原始文章(https://www.javaspecialists.eu/archive/Issue078.html)的核心代码,以及不安全版本中对这个问题的另一个回答中的一些内容。

我希望有人觉得它有用。

public class JavaSize {

    private static final int NR_BITS = Integer.valueOf(System.getProperty("sun.arch.data.model"));
    private static final int BYTE = 8;
    private static final int WORD = NR_BITS / BYTE;
    private static final int HEADER_SIZE = 8;

    public static int sizeOf(Class<?> clazz) {
        int result = 0;

        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                if (!Modifier.isStatic(fields[i].getModifiers())) {
                    if (fields[i].getType().isPrimitive()) {
                        Class<?> primitiveClass = fields[i].getType();
                        if (primitiveClass == boolean.class || primitiveClass == byte.class) {
                            result += 1;
                        } else if (primitiveClass == short.class) {
                            result += 2;
                        } else if (primitiveClass == int.class || primitiveClass == float.class) {
                            result += 4;
                        } else if (primitiveClass == double.class || primitiveClass == long.class) {
                            result += 8;
                        }

                    } else {
                        // assume compressed references.
                        result += 4;
                    }
                }
            }

            clazz = clazz.getSuperclass();

            // round up to the nearest WORD length.
            if ((result % WORD) != 0) {
                result += WORD - (result % WORD);
            }
        }

        result += HEADER_SIZE;

        return result;
    }
 }

你必须利用反射在物体上行走。做的时候要小心:

仅仅分配一个对象在JVM中就有一些开销。这个量因JVM而异,所以可以将此值作为参数。至少让它成为一个常量(8字节?),并应用于分配的任何东西。 仅仅因为一个字节理论上是1个字节,并不意味着内存中只需要一个字节。 在对象引用中会有循环,因此您需要使用object-equals作为比较器来保留HashMap或类似的东西,以消除无限循环。

@jodonnell:我喜欢你的解决方案的简单性,但许多对象是不可序列化的(所以这将抛出一个异常),字段可以是暂时的,对象可以覆盖标准方法。