关于内存:在Java中,确定对象大小的最佳方法是什么?

In Java, what is the best way to determine the size of an object?

例如,假设我有一个应用程序,可以用成堆的数据行读取csv文件。我根据数据类型给用户一个行数的摘要,但是我要确保我不读取太多的数据行并导致OutOfMemoryErrors。每一行转换为一个对象。有没有一种简单的方法可以通过编程的方式找出那个对象的大小?是否有一个引用定义了VM的基本类型和对象引用的大小?

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


您可以使用java.lang.instrument包

编译该类并将其放入jar中:

1
2
3
4
5
6
7
8
9
10
11
12
13
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中添加以下内容:

1
Premain-Class: ObjectSizeFetcher

使用GetObjectSize:

1
2
3
4
5
6
7
8
public class C {
    private int x;
    private int y;

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

调用:

1
java -javaagent:ObjectSizeFetcherAgent.jar C


您应该使用jol,它是作为OpenJDK项目的一部分开发的工具。

JOL (Java Object Layout) is the tiny toolbox to analyze object layout schemes in JVMs. These tools are using Unsafe, JVMTI, and Serviceability Agent (SA) heavily to decoder the actual object layout, footprint, and references. This makes JOL much more accurate than other tools relying on heap dumps, specification assumptions, etc.

要获得原语、引用和数组元素的大小,请使用VMSupport.vmDetails()。在64位Windows上运行的Oracle JDK 1.8.0_(用于以下所有示例)上,此方法返回

1
2
3
4
5
6
Running 64-bit HotSpot VM.
Using compressed oop with 0-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

您可以使用ClassLayout.parseClass(Foo.class).toPrintable()获得对象实例的浅层大小(可选地将实例传递给toPrintable)。这只是该类的单个实例所占用的空间;它不包括该类引用的任何其他对象。它包括对象头、字段对齐和填充的VM开销。对于java.util.regex.Pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java.util.regex.Pattern object internals:
 OFFSET  SIZE        TYPE DESCRIPTION                    VALUE
      0     4             (object header)                01 00 00 00 (0000 0001 0000 0000 0000 0000 0000 0000)
      4     4             (object header)                00 00 00 00 (0000 0000 0000 0000 0000 0000 0000 0000)
      8     4             (object header)                cb cf 00 20 (1100 1011 1100 1111 0000 0000 0010 0000)
     12     4         int Pattern.flags                  0
     16     4         int Pattern.capturingGroupCount    1
     20     4         int Pattern.localCount             0
     24     4         int Pattern.cursor                 48
     28     4         int Pattern.patternLength          0
     32     1     boolean Pattern.compiled               true
     33     1     boolean Pattern.hasSupplementary       false
     34     2             (alignment/padding gap)        N/A
     36     4      String Pattern.pattern                (object)
     40     4      String Pattern.normalizedPattern      (object)
     44     4        Node Pattern.root                   (object)
     48     4        Node Pattern.matchRoot              (object)
     52     4       int[] Pattern.buffer                 null
     56     4         Map Pattern.namedGroups            null
     60     4 GroupHead[] Pattern.groupNodes             null
     64     4       int[] Pattern.temp                   null
     68     4             (loss due to the next object alignment)
Instance size: 72 bytes (reported by Instrumentation API)
Space losses: 2 bytes internal + 4 bytes external = 6 bytes total

您可以使用GraphLayout.parseInstance(obj).toFootprint()获得对象实例的深度视图。当然,封装外形中的某些对象可能是共享的(也可以从其他对象引用),因此当该对象被垃圾收集时,它对可回收空间的过度估计。对于Pattern.compile("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")的结果(取自此答案),jol报告的总占用空间为1840字节,其中只有72个是模式实例本身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java.util.regex.Pattern instance footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1       112       112   [C
         3       272       816   [Z
         1        24        24   java.lang.String
         1        72        72   java.util.regex.Pattern
         9        24       216   java.util.regex.Pattern$1
        13        24       312   java.util.regex.Pattern$5
         1        16        16   java.util.regex.Pattern$Begin
         3        24        72   java.util.regex.Pattern$BitClass
         3        32        96   java.util.regex.Pattern$Curly
         1        24        24   java.util.regex.Pattern$Dollar
         1        16        16   java.util.regex.Pattern$LastNode
         1        16        16   java.util.regex.Pattern$Node
         2        24        48   java.util.regex.Pattern$Single
        40                1840   (total)

如果您使用GraphLayout.parseInstance(obj).toPrintable(),jol将告诉您每个被引用对象的字段取消引用的地址、大小、类型、值和路径,尽管这通常太详细而不实用。对于正在进行的模式示例,您可能会得到以下内容。(两次运行之间地址可能会发生变化。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
java.util.regex.Pattern object externals:
          ADDRESS       SIZE TYPE                             PATH                           VALUE
         d5e5f290         16 java.util.regex.Pattern$Node     .root.next.atom.next           (object)
         d5e5f2a0        120 (something else)                 (somewhere else)               (something else)
         d5e5f318         16 java.util.regex.Pattern$LastNode .root.next.next.next.next.next.next.next (object)
         d5e5f328      21664 (something else)                 (somewhere else)               (something else)
         d5e647c8         24 java.lang.String                 .pattern                       (object)
         d5e647e0        112 [C                               .pattern.value                 [^, [, a, -, z, A, -, Z, 0, -, 9, _, ., +, -, ], +, @, [, a, -, z, A, -, Z, 0, -, 9, -, ], +, \, ., [, a, -, z, A, -, Z, 0, -, 9, -, ., ], +, $]
         d5e64850        448 (something else)                 (somewhere else)               (something else)
         d5e64a10         72 java.util.regex.Pattern                                         (object)
         d5e64a58        416 (something else)                 (somewhere else)               (something else)
         d5e64bf8         16 java.util.regex.Pattern$Begin    .root                          (object)
         d5e64c08         24 java.util.regex.Pattern$BitClass .root.next.atom.val$rhs        (object)
         d5e64c20        272 [Z                               .root.next.atom.val$rhs.bits   [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e64d30         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64d48         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e64d60         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64d78         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e64d90         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64da8         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e64dc0         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs (object)
         d5e64dd8         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs        (object)
         d5e64df0         24 java.util.regex.Pattern$5        .root.next.atom                (object)
         d5e64e08         32 java.util.regex.Pattern$Curly    .root.next                     (object)
         d5e64e28         24 java.util.regex.Pattern$Single   .root.next.next                (object)
         d5e64e40         24 java.util.regex.Pattern$BitClass .root.next.next.next.atom.val$rhs (object)
         d5e64e58        272 [Z                               .root.next.next.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e64f68         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e64f80         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$lhs.val$rhs (object)
         d5e64f98         24 java.util.regex.Pattern$5        .root.next.next.next.atom.val$lhs.val$lhs (object)
         d5e64fb0         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$rhs (object)
         d5e64fc8         24 java.util.regex.Pattern$5        .root.next.next.next.atom.val$lhs (object)
         d5e64fe0         24 java.util.regex.Pattern$5        .root.next.next.next.atom      (object)
         d5e64ff8         32 java.util.regex.Pattern$Curly    .root.next.next.next           (object)
         d5e65018         24 java.util.regex.Pattern$Single   .root.next.next.next.next      (object)
         d5e65030         24 java.util.regex.Pattern$BitClass .root.next.next.next.next.next.atom.val$rhs (object)
         d5e65048        272 [Z                               .root.next.next.next.next.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e65158         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e65170         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e65188         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e651a0         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$rhs (object)
         d5e651b8         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs.val$lhs (object)
         d5e651d0         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs (object)
         d5e651e8         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom (object)
         d5e65200         32 java.util.regex.Pattern$Curly    .root.next.next.next.next.next (object)
         d5e65220        120 (something else)                 (somewhere else)               (something else)
         d5e65298         24 java.util.regex.Pattern$Dollar   .root.next.next.next.next.next.next (object)

"(something else)"条目描述堆中不属于此对象图的其他对象。

最好的jol文档是jol存储库中的jol示例。这些示例演示了常见的jol操作,并展示了如何使用jol分析VM和垃圾收集器内部。


几年前,JavaWork发表了一篇文章,讨论了复合和潜在嵌套Java对象的大小,它们基本上是通过在爪哇中创建一个siZeFor()实现的。该方法基本上建立在其他实验中,人们在实验中识别原语和典型Java对象的大小,然后将该知识应用于递归地遍历对象图以统计总大小的方法。

它总是比本机C实现稍差一些,这仅仅是因为类背后发生了一些事情,但它应该是一个很好的指示器。

或者,一个名为sizeof的sourceforge项目,它提供了一个带有sizeof()实现的java5库。

P.S.不要使用序列化方法,序列化对象的大小和它在活动时消耗的内存量之间没有相关性。


首先,"对象的大小"在Java中不是一个定义良好的概念。你可以指的是对象本身,只有它的成员,对象和它所指的所有对象(参考图)。您可以是指内存大小或磁盘大小。并且允许JVM优化字符串之类的东西。

因此,唯一正确的方法是使用一个好的探查器(我使用yourkit)询问JVM,这可能不是您想要的。

但是,从上面的描述来看,似乎每一行都是自包含的,并且没有大的依赖树,因此序列化方法可能是大多数JVM的一个很好的近似方法。最简单的方法如下:

1
2
3
4
5
6
 Serializable ser;
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(baos);
 oos.writeObject(ser);
 oos.close();
 return baos.size();

请记住,如果有具有公共引用的对象,这将不会给出正确的结果,而且序列化的大小并不总是与内存中的大小匹配,但这是一个很好的近似值。如果您将bytearrayOutputstream大小初始化为一个合理的值,代码将更高效。


我偶然发现了一个Java类"jdk.nashorn.internal.ir.debug.objectsizecalculator",已经在jdk中,它很容易使用,而且对于确定一个物体的大小很有用。

1
2
3
4
5
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]));

结果:

1
2
3
4
5
164192
48
16
48
416


如果您只想知道在您的JVM中使用了多少内存,有多少是空闲的,您可以尝试这样做:

1
2
3
4
5
6
7
8
9
10
// 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内存之前,尽可能多地读取行"的逻辑。


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

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


许多其他答案都提供了较浅的大小——例如,没有任何键或值的哈希图的大小,这不太可能是您想要的。

jamm项目使用上面的java.lang.instrumentation包,但会遍历树,因此可以给您提供深层内存使用。

1
new MemoryMeter().measureDeep(myHashMap);

https://github.com/jbelis/jamm


你必须使用反射来移动物体。像你这样小心:

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

@Jodonnell:我喜欢您的解决方案的简单性,但是许多对象是不可序列化的(因此这会引发异常),字段可以是临时的,对象可以覆盖标准方法。


您必须使用一个工具来测量它,或者手工估计它,这取决于您使用的JVM。

每个对象都有一些固定的开销。它是特定于JVM的,但我通常估计有40个字节。然后你必须看看班上的成员。对象引用是32位(64位)JVM中的4(8)个字节。基本类型包括:

  • 布尔值和字节:1字节
  • 字符和短:2字节
  • int和float:4字节
  • 长和双字节:8字节

数组遵循相同的规则;也就是说,它是一个对象引用,因此在对象中需要4(或8)个字节,然后它的长度乘以它的元素的大小。

尝试通过对Runtime.freeMemory()的调用以编程的方式来实现这一点并不能给您带来太多的准确性,因为对垃圾收集器的异步调用等。使用-xrunhprof或其他工具对堆进行分析会给您带来最准确的结果。


还有内存测量工具(以前在Google代码,现在在Github上),它很简单,并在商业友好的Apache2.0许可证下发布,在类似的问题中讨论过。

如果你想测量内存字节消耗,它也需要命令行参数到Java解释器,但是在我使用它的场景中,它看起来很好。


EDCOX1的1个类提供了一个获得Java对象大小的好方法,但是它要求您定义一个EDCOX1,2个,并用Java代理运行程序。当您不需要任何代理,然后必须为应用程序提供一个虚拟的JAR代理时,这是非常无聊的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
2
3
4
5
6
7
System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

do your job here

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

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

为了获得更可靠的结果,您可以运行作业n次,然后将使用的内存除以n,得到一次运行需要多少内存。更重要的是,你可以把整件事做更多次,并得出平均值。


这里有一个实用程序,我使用一些链接的例子来处理32位、64位和64位的压缩OOP。它使用sun.misc.Unsafe

它使用EDCOX1×1来获得一个本地指针的大小和EDCOX1(2)对Java引用的大小的影响。

它使用已知类的字段偏移量计算对象的基本大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Stack;
import sun.misc.Unsafe;

/** Usage:
 * MemoryUtil.sizeOf( object )
 * MemoryUtil.deepSizeOf( object )
 * MemoryUtil.ADDRESS_MODE
 */

public class MemoryUtil
{
    private MemoryUtil()
    {
    }

    public static enum AddressMode
    {
        /** Unknown address mode. Size calculations may be unreliable. */
        UNKNOWN,
        /** 32-bit address mode using 32-bit references. */
        MEM_32BIT,
        /** 64-bit address mode using 64-bit references. */
        MEM_64BIT,
        /** 64-bit address mode using 32-bit compressed references. */
        MEM_64BIT_COMPRESSED_OOPS
    }

    /** The detected runtime address mode. */
    public static final AddressMode ADDRESS_MODE;

    private static final Unsafe UNSAFE;

    private static final long ADDRESS_SIZE; // The size in bytes of a native pointer: 4 for 32 bit, 8 for 64 bit
    private static final long REFERENCE_SIZE; // The size of a Java reference: 4 for 32 bit, 4 for 64 bit compressed oops, 8 for 64 bit
    private static final long OBJECT_BASE_SIZE; // The minimum size of an Object: 8 for 32 bit, 12 for 64 bit compressed oops, 16 for 64 bit
    private static final long OBJECT_ALIGNMENT = 8;

    /** Use the offset of a known field to determine the minimum size of an object. */
    private static final Object HELPER_OBJECT = new Object() { byte b; };


    static
    {
        try
        {
            // Use reflection to get a reference to the 'Unsafe' object.
            Field f = Unsafe.class.getDeclaredField("theUnsafe" );
            f.setAccessible( true );
            UNSAFE = (Unsafe) f.get( null );

            OBJECT_BASE_SIZE = UNSAFE.objectFieldOffset( HELPER_OBJECT.getClass().getDeclaredField("b" ) );

            ADDRESS_SIZE = UNSAFE.addressSize();
            REFERENCE_SIZE = UNSAFE.arrayIndexScale( Object[].class );

            if( ADDRESS_SIZE == 4 )
            {
                ADDRESS_MODE = AddressMode.MEM_32BIT;
            }
            else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 8 )
            {
                ADDRESS_MODE = AddressMode.MEM_64BIT;
            }
            else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 4 )
            {
                ADDRESS_MODE = AddressMode.MEM_64BIT_COMPRESSED_OOPS;
            }
            else
            {
                ADDRESS_MODE = AddressMode.UNKNOWN;
            }
        }
        catch( Exception e )
        {
            throw new Error( e );
        }
    }


    /** Return the size of the object excluding any referenced objects. */
    public static long shallowSizeOf( final Object object )
    {
        Class<?> objectClass = object.getClass();
        if( objectClass.isArray() )
        {
            // Array size is base offset + length * element size
            long size = UNSAFE.arrayBaseOffset( objectClass )
                    + UNSAFE.arrayIndexScale( objectClass ) * Array.getLength( object );
            return padSize( size );
        }
        else
        {
            // Object size is the largest field offset padded out to 8 bytes
            long size = OBJECT_BASE_SIZE;
            do
            {
                for( Field field : objectClass.getDeclaredFields() )
                {
                    if( (field.getModifiers() & Modifier.STATIC) == 0 )
                    {
                        long offset = UNSAFE.objectFieldOffset( field );
                        if( offset >= size )
                        {
                            size = offset + 1; // Field size is between 1 and PAD_SIZE bytes. Padding will round up to padding size.
                        }
                    }
                }
                objectClass = objectClass.getSuperclass();
            }
            while( objectClass != null );

            return padSize( size );
        }
    }


    private static final long padSize( final long size )
    {
        return (size + (OBJECT_ALIGNMENT - 1)) & ~(OBJECT_ALIGNMENT - 1);
    }


    /** Return the size of the object including any referenced objects. */
    public static long deepSizeOf( final Object object )
    {
        IdentityHashMap<Object,Object> visited = new IdentityHashMap<Object,Object>();
        Stack<Object> stack = new Stack<Object>();
        if( object != null ) stack.push( object );

        long size = 0;
        while( !stack.isEmpty() )
        {
            size += internalSizeOf( stack.pop(), stack, visited );
        }
        return size;
    }


    private static long internalSizeOf( final Object object, final Stack<Object> stack, final IdentityHashMap<Object,Object> visited )
    {
        // Scan for object references and add to stack
        Class<?> c = object.getClass();
        if( c.isArray() && !c.getComponentType().isPrimitive() )
        {
            // Add unseen array elements to stack
            for( int i = Array.getLength( object ) - 1; i >= 0; i-- )
            {
                Object val = Array.get( object, i );
                if( val != null && visited.put( val, val ) == null )
                {
                    stack.add( val );
                }
            }
        }
        else
        {
            // Add unseen object references to the stack
            for( ; c != null; c = c.getSuperclass() )
            {
                for( Field field : c.getDeclaredFields() )
                {
                    if( (field.getModifiers() & Modifier.STATIC) == 0
                            && !field.getType().isPrimitive() )
                    {
                        field.setAccessible( true );
                        try
                        {
                            Object val = field.get( object );
                            if( val != null && visited.put( val, val ) == null )
                            {
                                stack.add( val );
                            }
                        }
                        catch( IllegalArgumentException e )
                        {
                            throw new RuntimeException( e );
                        }
                        catch( IllegalAccessException e )
                        {
                            throw new RuntimeException( e );
                        }
                    }
                }
            }
        }

        return shallowSizeOf( object );
    }
}


如果你要求的是方法调用,那么就没有方法调用。通过一点研究,我想你可以自己写。一个特定的实例有一个固定的大小,这个大小是由引用的数量和原语值加上实例簿记数据得出的。您只需浏览对象图。行类型的变化越小,就越容易。

如果这太慢或只是比它的价值更多的麻烦,总是有很好的老式的排数规则的拇指。


我曾经写过一个快速测试来评估:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class Test1 {

    // non-static nested
    class Nested { }

    // static nested
    static class StaticNested { }

    static long getFreeMemory () {
        // waits for free memory measurement to stabilize
        long init = Runtime.getRuntime().freeMemory(), init2;
        int count = 0;
        do {
            System.out.println("waiting..." + init);
            System.gc();
            try { Thread.sleep(250); } catch (Exception x) { }
            init2 = init;
            init = Runtime.getRuntime().freeMemory();
            if (init == init2) ++ count; else count = 0;
        } while (count < 5);
        System.out.println("ok..." + init);
        return init;
    }

    Test1 () throws InterruptedException {

        Object[] s = new Object[10000];
        Object[] n = new Object[10000];
        Object[] t = new Object[10000];

        long init = getFreeMemory();

        //for (int j = 0; j < 10000; ++ j)
        //    s[j] = new Separate();

        long afters = getFreeMemory();

        for (int j = 0; j < 10000; ++ j)
            n[j] = new Nested();

        long aftersn = getFreeMemory();

        for (int j = 0; j < 10000; ++ j)
            t[j] = new StaticNested();

        long aftersnt = getFreeMemory();

        System.out.println("separate:     " + -(afters - init) +" each=" + -(afters - init) / 10000);
        System.out.println("nested:       " + -(aftersn - afters) +" each=" + -(aftersn - afters) / 10000);
        System.out.println("static nested:" + -(aftersnt - aftersn) +" each=" + -(aftersnt - aftersn) / 10000);

    }

    public static void main (String[] args) throws InterruptedException {
        new Test1();
    }

}

一般的概念是分配对象和测量空闲堆空间中的变化。密钥是getFreeMemory(),它请求GC运行并等待报告的空闲堆大小稳定下来。以上输出为:

1
2
nested:        160000 each=16
static nested: 160000 each=16

这就是我们所期望的,给定对齐行为和可能的堆块头开销。

这里的仪器方法在公认的答案中是最准确的。我描述的方法是准确的,但仅在没有其他线程创建/丢弃对象的受控条件下。


只需使用Java Visual VM即可。

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

它还有一个OQL(对象查询语言)控制台,允许您做许多有用的事情,其中一个是sizeof(o)


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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;
    }    

}

我已经用基本类型、字符串和一些普通类测试了这个解决方案。也可能没有涵盖的案例。

更新:修改示例以支持数组对象的内存足迹计算。


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


假设我声明一个名为Complex的类,比如:

1
2
3
4
5
6
7
public class Complex {

    private final long real;
    private final long imaginary;

    // omitted
}

为了查看分配给此类活动实例的内存量:

1
2
3
4
5
$ jmap -histo:live <pid> | grep Complex

 num     #instances         #bytes  class name (module)
-------------------------------------------------------
 327:             1             32  Complex

这个答案与对象大小无关,但当您使用数组来容纳对象时,它将为对象分配多少内存大小。

因此,数组、列表或映射所有这些集合将不会真正存储对象(仅在基元时需要实际的对象内存大小),它将只存储这些对象的引用。

现在是江户十一〔五〕。

  • (4/8字节)取决于(32/64位)操作系统

基元

1
2
int   [] intArray    = new int   [1]; will require 4 bytes.
long  [] longArray   = new long  [1]; will require 8 bytes.

物体

1
2
Object[] objectArray = new Object[1]; will require 4 bytes. The object can be any user defined Object.
Long  [] longArray   = new Long  [1]; will require 4 bytes.

我的意思是说所有的对象引用只需要4个字节的内存。它可以是字符串引用或双对象引用,但取决于对象创建,所需的内存将有所不同。

例如,如果我为下面的类ReferenceMemoryTest创建对象,那么将创建4+4+4=12字节的内存。当您试图初始化引用时,内存可能会有所不同。

1
2
3
4
5
 class ReferenceMemoryTest {
    public String refStr;
    public Object refObj;
    public Double refDoub;
}

因此,在创建对象/引用数组时,它的所有内容都将被空引用占据。我们知道每个引用需要4个字节。

最后,下面代码的内存分配是20字节。

referenceMemoryTest ref1=new referenceMemoryTest();(4(ref1)+12=16字节)referenceMemoryTest ref2=ref1;(4(ref2)+16=20字节)


1
2
3
4
5
6
long heapSizeBefore = Runtime.getRuntime().totalMemory();

// Code for object construction
...
long heapSizeAfter = Runtime.getRuntime().totalMemory();
long size = heapSizeAfter - heapSizeBefore;

由于对象的创建,大小会增加JVM的内存使用量,这通常是对象的大小。


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

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

返回字节大小

我用jsonarray对象检查了它,将它写到一个文件中。它给出了物体的大小。


我怀疑你是否想通过编程来实现它,除非你只想实现一次,并将其存储起来以备将来使用。这是一件昂贵的事情。在Java中没有siZOFF()运算符,即使存在,也只计算引用到其他对象的成本和基元的大小。

一种方法是将其序列化为文件,并查看文件的大小,如下所示:

1
2
3
4
Serializable myObject;
ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("obj.ser"));
oos.write (myObject);
oos.close ();

当然,这假定每个对象都是不同的,并且不包含对其他任何对象的非瞬时引用。

另一种策略是对每个对象进行反射并检查其成员,然后将大小相加(布尔值字节=1字节,短字符=2字节等),沿着成员层次结构向下工作。但是,这是乏味和昂贵的,并且最终做的和序列化策略一样。