在Java中计算对象的大小

180

我想记录一个项目中对象所占用的内存量(以字节为单位),以便比较数据结构的大小。但是在Java中似乎没有相应的方法来实现这个目的。据说,在C/C ++中有一个名为sizeOf()的方法,但在Java中不存在。我尝试使用Runtime.getRuntime().freeMemory()在创建对象之前和之后记录JVM中的可用内存,然后记录差异,但无论数据结构中有多少元素,它只会给出0或131304,而且其他任何中间值都没有。请求帮助!


6
在Java中,确定对象大小的最佳方法是使用Instrumentation类的getObjectSize(Object)方法。该方法返回对象本身在堆上使用的字节数。请注意,此方法不包括任何嵌套对象的大小。如果您需要考虑嵌套对象,请遍历整个对象并将所有嵌套对象的大小相加。 - Sergey Grinev
1
你可能想尝试使用totalMemory()和freeMemory()方法,然后对它们进行减法运算。 - Matt
5
我建议您查看Jbellis JAMM(https://github.com/jbellis/jamm)。它依赖于上面提到的仪器代理,但封装了一些有用的实用程序以深入测量整个对象图的占用空间。对我来说看起来简单而且很酷。 - Claudio
它仅适用于原始结构,而不适用于具有集合或其他内容的结构。 int [] .class的大小可能只有几个字节,但new int [1e9]的大小将是几个千兆字节。 - Mikhail Ionkin
3个回答

99
你可以使用java.lang.instrumentation 包。
它提供了一种方法,可以用于获取实现特定的对象大小近似值,以及与对象相关的开销。
Sergey链接的答案有一个很好的示例,我会在此重新发布,但你应该已经看过他的评论了:
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);
    }
}
使用 getObjectSize
public class C {
    private int x;
    private int y;

    public static void main(String [] args) {
        System.out.println(ObjectSizeFetcher.getObjectSize(new C()));
    }
}

来源


1
唯一访问Instrumentation接口实例的方式是通过以指示代理类的方式启动JVM - 请参见软件包规范。 Instrumentation实例传递给代理类的premain方法。 一旦代理获取了Instrumentation实例,代理可以随时调用实例上的方法。那我该如何在Eclipse中操作呢? - Tyler Petrochko
我不需要指定JVM如何启动吗?我尝试了,但它一直给我NullPointerException。 - Tyler Petrochko
2
请编辑您上面的帖子并写下您尝试过的内容。 - Hunter McMillen
185
不知道为什么这个答案会获得如此多的赞。它完全忽略了编译代理类并将其作为参数传递给JVM是必要的事实。不这样做将使检测变为空,并触发NullPointerException。以下是正确的答案:https://dev59.com/TnVD5IYBdhLWcg3wNIzc - arkon
当尝试查找我的Web会话大小时,这种方法肯定存在问题:它报告了40,而ObjectGraphMeasurer(https://github.com/DimitrisAndreou/memory-measurer)则报告“Footprint{Objects=34, References=52, Primitives=[float, int x 19, char x 257, long x 2, byte x 68557]}”。显然,ObjectGraphMeasurer才是赢家。 - Yuci
长整型数值是字节数吗? - Anirudh Kashyap

93

看看https://github.com/DimitrisAndreou/memory-measurer,Guava在内部使用它,并且ObjectGraphMeasurer特别易于直接使用,无需任何特殊的命令行参数。

import objectexplorer.ObjectGraphMeasurer;

public class Measurer {

  public static void main(String[] args) {
    Set<Integer> hashset = new HashSet<Integer>();
    Random random = new Random();
    int n = 10000;
    for (int i = 1; i <= n; i++) {
      hashset.add(random.nextInt());
    }
    System.out.println(ObjectGraphMeasurer.measure(hashset));
  }
}

2
我之前用过内存测量工具,没错。(我参与了Guava的开发,并且提到我们在内部使用它。; ) - Louis Wasserman
8
我怀疑最常使用 Stack Overflow 的用户是为了拖延时间才来这里——所以当然我们一直在这里,而且能立即回复评论。 - Louis Wasserman
1
这取决于您的JVM。如果您想测量字节,则应该使用同一项目中的MemoryMeasurer;请按照那里的说明操作。 - Louis Wasserman
那么你必须添加依赖关系?那又怎样? - Louis Wasserman
1
@rodi 结果是线性的,但由于反射而具有相当高的常数因子。 - Louis Wasserman
显示剩余20条评论

19

java.lang.instrument.Instrumentation 类提供了一种很好的方法来获取 Java 对象的大小,但是它要求你定义一个 premain 并且使用 Java 代理运行你的程序,如果你不需要任何代理,这就非常无聊了,然后你必须为你的应用程序提供一个虚假的 Jar 代理。

因此,我使用来自sun.miscUnsafe 类的替代方案。所以,考虑到根据处理器架构的对象堆对齐和计算最大字段偏移量,可以测量 Java 对象的大小。在下面的示例中,我使用辅助类 UtilUnsafe 来获取对sun.misc.Unsafe 对象的引用。

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 MIN_SIZE = 16; 

public static int sizeOf(Class src){
    //
    // Get the instance fields of src class
    // 
    List<Field> instanceFields = new LinkedList<Field>();
    do{
        if(src == Object.class) return MIN_SIZE;
        for (Field f : src.getDeclaredFields()) {
            if((f.getModifiers() & Modifier.STATIC) == 0){
                instanceFields.add(f);
            }
        }
        src = src.getSuperclass();
    }while(instanceFields.isEmpty());
    //
    // Get the field with the maximum offset
    //  
    long maxOffset = 0;
    for (Field f : instanceFields) {
        long offset = UtilUnsafe.UNSAFE.objectFieldOffset(f);
        if(offset > maxOffset) maxOffset = offset; 
    }
    return  (((int)maxOffset/WORD) + 1)*WORD; 
}
class UtilUnsafe {
    public static final sun.misc.Unsafe UNSAFE;

    static {
        Object theUnsafe = null;
        Exception exception = null;
        try {
            Class<?> uc = Class.forName("sun.misc.Unsafe");
            Field f = uc.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            theUnsafe = f.get(uc);
        } catch (Exception e) { exception = e; }
        UNSAFE = (sun.misc.Unsafe) theUnsafe;
        if (UNSAFE == null) throw new Error("Could not obtain access to sun.misc.Unsafe", exception);
    }
    private UtilUnsafe() { }
}

1
我不确定这个表达式:((int)maxOffset/WORD) + 1)*WORD 是否正确,但我认为它是有效的,因为我很确定64位变量在64位虚拟机中永远不会跨越8字节边界。除此之外,代码很好,尽管你当然可以摆脱整个地图制作的工作。我制作了自己的版本,还处理了对象数组,链接在这里:http://tinybrain.de/1011517(仍需要添加原始数组)。 - Stefan Reich

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