在 Java 中,确定对象大小的最佳方法是啥?
Posted
技术标签:
【中文标题】在 Java 中,确定对象大小的最佳方法是啥?【英文标题】:In Java, what is the best way to determine the size of an object?在 Java 中,确定对象大小的最佳方法是什么? 【发布时间】:2010-09-08 07:36:48 【问题描述】:我有一个应用程序可以读取包含大量数据行的 CSV 文件。我给用户一个基于数据类型的行数摘要,但我想确保我不会读入太多的数据行并导致OutOfMemoryError
s。每行转换为一个对象。有没有一种简单的方法可以以编程方式找出该对象的大小?是否有定义VM
的原始类型和对象引用有多大的参考?
现在,我的代码说读取最多 32,000 行,但我还想要代码说读取尽可能多的行,直到我使用 32MB 的记忆。也许这是一个不同的问题,但我仍然想知道。
【问题讨论】:
我用 mvn 配置添加了我的代理并在此处解释了如何:***.com/a/36102269/711855 【参考方案1】:您可以使用java.lang.instrument
package。
编译这个类并将其放入一个 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
【讨论】:
@Stefan 很好的提示!您能告诉我,使用您描述的方法,byte[0]
、byte[1]
、byte[5]
、int[0]
、int[1]
、int[2]
的大小是多少?如果结果包括数组长度和内存对齐的开销,那就太好了。
我尝试了这个并且得到了奇怪且无用的结果。字符串总是 32,不管大小。我认为这可能是指针大小,但对于我创建的另一个不可变类,我得到了 24。它适用于原语,但你并不需要一个程序来告诉你一个 char 有多大。
@Brel 此解决方案只是文档中指定的“指定对象消耗的存储量的近似值”。另外我想作者决定将 String 的大小设置为 32 字节(只有指针?),因为 Java 的 String 池,这使得很难说 String 实例是共享的(存储在池中)还是本地和唯一的类。
如果不导出 jar,我如何使用 ObjectSizeFetcher?我在 Eclipse 中测试了 java 项目。
@brel 不管实际长度如何,一个字符串只有 32 个字节的原因是因为字符串的可变长度部分存储在 char[] 中,这是它自己的对象。要得到一个对象的真实大小,你需要把它自己的大小和它引用的每个对象的大小相加。【参考方案2】:
您应该使用jol,这是作为 OpenJDK 项目的一部分开发的工具。
JOL(Java 对象布局)是用于分析 JVM 中对象布局方案的微型工具箱。这些工具大量使用 Unsafe、JVMTI 和 Serviceability Agent (SA) 来解码实际的对象布局、占用空间和引用。这使得 JOL 比其他依赖堆转储、规范假设等的工具更准确。
要获取基元、引用和数组元素的大小,请使用VMSupport.vmDetails()
。在 64 位 Windows 上运行的 Oracle JDK 1.8.0_40(用于以下所有示例),此方法返回
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
:
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-.]+$")
(取自this answer)的结果,jol 报告总占用空间为 1840 个字节,其中只有 72 个是 Pattern 实例本身。
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 会告诉您对每个被引用对象的字段解引用的地址、大小、类型、值和路径,尽管这通常过于详细而无用。对于正在进行的模式示例,您可能会得到以下信息。 (地址可能会在运行之间发生变化。)
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)
“(其他)”条目describe other objects in the heap that are not part of this object graph。
最好的 jol 文档是 jol 存储库中的 jol samples。这些示例演示了常见的 jol 操作,并展示了如何使用 jol 来分析 VM 和垃圾收集器的内部结构。
【讨论】:
这个答案应该有更多的赞成票。绝对是一个很好的检查选择。编辑:检查这是在今年添加的,而在 08 年提出了问题。可能是目前执行 OP 要求的最佳和最简单的选择。 工具作者写了a blog post about Jol。 确定对象“obj”的大小使用:org.openjdk.jol.info.GraphLayout.parseInstance(obj).totalSize(); 请注意,vmDetails
现在是 VM.current().details()
。
查看GraphLayout.parseInstance(instance).toFootprint()
我发现了解对象大小更有用【参考方案3】:
无意中发现了一个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
【讨论】:
同样在这里,我尝试了上面提出的其他解决方案并遇到了 ObjectSizeCalculator。我相信之前没有人提到过,因为它最近作为项目Nashorn 的一部分在 JDK 8 上被引入。但是我在网上没有找到任何关于这个类的官方文档。 它似乎没有考虑字符串长度。它只是堆栈上的大小吗? 我有一个哈希图,其中 com.carrotsearch.RamUsageEstimator 返回大约一半的 ObjectSizeCalculator。哪一个是真的? - 哪个更可靠? 请注意,ObjectSizeCalculator
仅在 HotSpot VM 上受支持
另外 jdk.nashorn.internal.ir.debug.ObjectSizeCalculator 不再出现在 JDK 11 上【参考方案4】:
几年前 Javaworld 有 an article on determining the size of composite and potentially nested Java objects,他们基本上是在 Java 中创建 sizeof() 实现。该方法基本上建立在其他工作的基础上,人们通过实验确定基元和典型 Java 对象的大小,然后将这些知识应用于递归遍历对象图以计算总大小的方法。
仅仅因为类的幕后发生的事情,它总是比原生 C 实现更不准确,但它应该是一个很好的指标。
或者一个适当地称为sizeof 的 SourceForge 项目,它提供了一个具有 sizeof() 实现的 Java5 库。
附:不要使用序列化的方法,序列化对象的大小和它存活时消耗的内存量之间没有关联。
【讨论】:
sizeof 实用程序可能是最快的方法。这基本上是 Stefan 所说的,但已经装在罐子里准备使用了。 答案中的链接已损坏【参考方案5】:首先,“对象的大小”在 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 的大小初始化为合理的值,代码会更高效。
【讨论】:
我喜欢这种方法。就物体大小而言,您离我们有多远。 非常简单有效。其他方法太乱了(特别是在 Eclipse RCP 内部)。谢谢。 序列化不会跟踪瞬态变量,并且默认的序列化方法以 UTF-8 写入字符串,因此任何 ANSI 字符将只占用一个字节。如果你有很多字符串,你的大小将太远以至于没有用。 虽然这可能无法给出确切的大小,但出于我的需要,我只需要比较 2 个对象,并且 SizeOf 不会从 Web 应用程序初始化。谢谢! YourKit的好推荐。其他替代方案是 VirtualVM 和 jvmmonitor【参考方案6】:如果您只想知道 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 内存之前尽可能多地读取行。”
【讨论】:
这不是一个好的解决方案,因为你永远不知道什么时候会发生垃圾收集,或者一次会为堆分配多少额外内存。 这是真的,我不打算用这个来解决这篇文章的主要问题,但它可能会帮助他以编程方式了解他何时接近达到最大堆大小。 此解决方案的其他问题是当您处于多线程环境中时(例如在 Web 服务器中)。其他线程可能正在执行并消耗内存。使用此近似值,您正在计算所有虚拟机中的已用内存。 另一个缺点是 freeMemory 返回一个近似值。尝试创建一个 javax.crypto.Cipher 对象。两次调用 freeMemory(估计密码的大小)之间的差异不是恒定的! 我相信您可以强制进行垃圾回收,因此您可以在这种方法中做一些事情。【参考方案7】:当我在 Twitter 工作时,我编写了一个用于计算深度对象大小的实用程序。它考虑了不同的内存模型(32 位、压缩 oops、64 位)、填充、子类填充,可在循环数据结构和数组上正常工作。你可以只编译这个 .java 文件;它没有外部依赖:
https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/objectsize/ObjectSizeCalculator.java
【讨论】:
希亚!我也想大声喊出您的presentation:幻灯片 15-20 非常适合帮助您直观地了解各种数据结构决策的成本。感谢您发布! “它没有外部依赖” - 因为番石榴什么时候不是外部依赖? 看起来与github.com/JetBrains/jdk8u_nashorn/blob/master/src/jdk/nashorn/… 非常相似? :O Guave 是一个外部依赖。【参考方案8】:许多其他答案都提供了浅尺寸 - 例如没有任何键或值的 HashMap 的大小,这可能不是您想要的。
jamm 项目使用上面的 java.lang.instrumentation 包,但会遍历树,因此可以为您提供深度内存使用。
new MemoryMeter().measureDeep(myHashMap);
https://github.com/jbellis/jamm
要使用 MemoryMeter,请使用“-javaagent:/jamm.jar”启动 JVM
【讨论】:
【参考方案9】:您必须使用反射来行走对象。做事要小心:
仅分配一个对象在 JVM 中有一些开销。数量因 JVM 而异,因此您可以将此值作为参数。至少将其设为常量(8 个字节?)并应用于任何已分配的内容。byte
理论上是 1 个字节并不意味着它只需要一个字节。
对象引用中会有循环,因此您需要保留HashMap
或类似的使用对象相等作为比较器以消除无限循环。
@jodonnell:我喜欢您的解决方案的简单性,但许多对象不是可序列化的(因此这会引发异常),字段可以是瞬态的,并且对象可以覆盖标准方法。
【讨论】:
Java 规范中不是定义了各种原语的大小吗? (§2.4.1) 不是在“它占用多少内存”的意义上,这是一个问题。仅在它们如何运作的意义上。例如,bytes、chars 和 shorts 在 Java 堆栈上占据了整个单词,即使它们使用舍入等操作。 这听起来类似于测量尺寸,如 Heinz 在他的时事通讯 #78 中所示:javaspecialists.eu/archive/Issue078.html。我用过。他的方法奏效了。【参考方案10】:你必须用工具测量它,或者手动估计它,这取决于你使用的JVM。
每个对象都有一些固定的开销。它是特定于 JVM 的,但我通常估计 40 个字节。然后你必须看看班级的成员。对象引用是 32 位(64 位)JVM 中的 4 (8) 个字节。原始类型是:
布尔值和字节:1 个字节 char 和 short:2 个字节 int 和 float:4 个字节 long 和 double:8 个字节数组遵循相同的规则;也就是说,它是一个对象引用,因此在您的对象中占用 4(或 8)个字节,然后将其长度乘以其元素的大小。
尝试通过调用Runtime.freeMemory()
以编程方式执行此操作不会给您带来太多准确性,因为对垃圾收集器的异步调用等。使用 -Xrunhprof 或其他工具分析堆将为您提供最准确的结果.
【讨论】:
@erickson 我不确定 sizeof(boolean)==1 查看此线程 (***.com/questions/1907318/…)。您能对此发表评论吗? @dma_k,Java 实际上没有真正的布尔值。布尔值的大小是数组外 4 个字节,boolean[]
内是 1 个字节。实际上所有非双/长类型的原语都是 4 个字节。后者是 8(答案也错误地将它们设为 4)
@bestsss:更准确地说,最小内存分配取决于 JVM 的平台和实现。堆上的对象也是对齐的,所以在总结所有大小后,需要四舍五入。【参考方案11】:
使用 JetBrains IntelliJ 时,首先在 File | 中启用“Attach memory agent”。设置 |构建、执行、部署 |调试器。
调试时,右键单击感兴趣的变量并选择“计算保留大小”:
【讨论】:
我在 Intellij 中没有看到这个 - 使用 2019.2。你用的是什么版本? 此错误:已连接到目标 VM,地址:'127.0.0.1:49538',传输:'socket' JDWP 退出错误 AGENT_ERROR_OUT_OF_MEMORY(188):PushLocalFrame:无法推送 JNI框架 [src/jdk.jdwp.agent/share/native/libjdwp/util.c:1560] 本机方法中的致命错误:JDWP PushLocalFrame:无法推送 JNI 框架,jvmtiError=AGENT_ERROR_OUT_OF_MEMORY(188) 与目标 VM 断开连接,地址:'127.0.0.1:49538',传输:'socket'进程以退出代码134完成(被信号6中断:SIGABRT)【参考方案12】:还有 Memory Measurer 工具(以前在 Google Code,现在在 GitHub),它很简单,在商业友好的 Apache 2.0 许可下发布,如similar question 中所述。
如果你想测量内存字节消耗,它也需要 java 解释器的命令行参数,但其他方面似乎工作得很好,至少在我使用它的场景中。
【讨论】:
【参考方案13】:java.lang.instrument.Instrumentation
类提供了一种获取 Java 对象大小的好方法,但它需要您定义一个 premain
并使用 Java 代理运行您的程序。当您不需要任何代理时,这非常无聊,然后您必须为您的应用程序提供一个虚拟 Jar 代理。
所以我使用sun.misc
中的Unsafe
类获得了替代解决方案。因此,根据处理器架构考虑对象堆对齐并计算最大字段偏移量,您可以衡量 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()
【讨论】:
有趣的方法,但这不是假设对象及其字段存储没有碎片吗? 是的,我不知道有任何 JVM 实现会产生这种碎片。 我不明白。碎片不是一个选项:) 让我们以对象 C 为例,它存储为对象 A 和 B 的字段。它不是在 A 或 B 中转移整个事物吗? 对不起,我不理解你的观点。根据我的解释,Java 中的对象不能存储在其他对象中,就像 C 结构或 .Net 中的值类型一样。因此,当您说:“对象 C 存储为对象 A 和 B 的字段”时,这意味着对象 A 和 B 具有存储对对象 C 的引用(指针)的字段。那么 A 和 B 的大小等于该字段的偏移量加上指向对象 C 的引用(指针)的大小。引用的大小是一个单词的大小。 哦,好的,我们正在谈论浅尺寸。我的错。【参考方案14】:不必搞乱检测等,如果您不需要知道对象的字节精确大小,您可以采用以下方法:
System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
do your job here
System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
通过这种方式,您可以在前后读取已用内存,并在获取已用内存之前调用 GC,您可以将“噪音”几乎降低到 0。
为了获得更可靠的结果,您可以运行您的作业 n 次,然后将使用的内存除以 n,得到一次运行占用的内存量。更重要的是,您可以将整个过程运行更多次并取平均值。
【讨论】:
System.gc()
不只是通知您要 GC 吗?根本不保证GC会被调用。
@reallynice。这是不安全的,因为你可能永远不会 GC 做什么或影响你的行之间的内存。因此,“在”两个 freeMemory 方法之间,GC 可以释放更多您不考虑的空间,因此您的对象看起来会更小
@MertSerimer“不安全”对我来说处于完全不同的水平:正如我也说过的那样,至多这不是那么准确。此外,您不能驱动 GC(如 Raildex 所述),但对于这种情况,我也建议在循环中插入它。如前所述,这只是一个快速、肮脏和近似的系统,如果结果不需要非常可靠,它就可以工作。
这有很多问题,但它确实给了你一个很好的东西。【参考方案15】:
我正在寻找满足以下要求的对象大小的运行时计算:
在运行时可用,无需包括检测。 在 Java 9+ 上工作,无需访问 Unsafe。 仅基于类。不是考虑字符串长度、数组长度等的深度 sizeOf。以下内容基于原始 java 专家文章 (https://www.javaspecialists.eu/archive/Issue078.html) 的核心代码以及该问题的另一个答案中的 Unsafe 版本中的一些内容。
我希望有人觉得它有用。
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;
【讨论】:
【参考方案16】:这是我使用一些链接示例制作的实用程序,用于处理带有压缩 OOP 的 32 位、64 位和 64 位。它使用sun.misc.Unsafe
。
它使用Unsafe.addressSize()
获取本机指针的大小,使用Unsafe.arrayIndexScale( Object[].class )
获取Java 引用的大小。
它使用已知类的字段偏移量来计算对象的基本大小。
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 );
【讨论】:
你用值测试过这个类吗?我试过了,但对我来说,值不正确!!!。 它给我的一个简单对象的值大约是正确的,但对于包含 1mio 对象的列表,它的值相差了 10 倍。不过,非常好的工作! 有趣。我已经使用 JDK7u67 在 Windows 7 x64 和 Linux 2.6.16/x86_64 上使用 32bit/64bit/oop 地址模式中的每一种对其进行了测试。我将它与 Eclipse Memory Analyzer 1.3.x 中分析的内存转储进行了比较。你用的是什么设置?你有我可以尝试的具体例子吗? 我能做的最好的选择。我不能使用Instrumentation
因为我没有启动tomcat,ObjectSizeCalculator
因为不确定VM 类型(HotSpot)和JOL
bacouse spring beans。我使用它并添加第二个参数来忽略单例,即 AbstractRefreshableApplicationContext.getBeanFactory().getSingletonMutex()
并重构 internalSizeOf
代码以忽略 Class 和 Enum
为了比较结果,请使用 ObjectSizeCalculator(计算整个服务器 1GB 到 10s)。 JOL 导致 MemError(6GB 不够),我没有得到相同的结果,可能是因为枚举。【参考方案17】:
没有方法调用,如果那是您所要求的。通过一些研究,我想你可以编写自己的。特定实例具有从引用和原始值的数量以及实例簿记数据派生的固定大小。您只需遍历对象图。行类型的变化越少,就越容易。
如果这太慢或只是比它的价值更多的麻烦,总是有很好的老式行计数经验法则。
【讨论】:
【参考方案18】:我写了一个快速测试来进行动态估计:
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 运行并等待报告的空闲堆大小稳定。上面的输出是:
nested: 160000 each=16
static nested: 160000 each=16
考虑到对齐行为和可能的堆块头开销,这正是我们所期望的。
此处接受的答案中详述的检测方法最准确。我描述的方法是准确的,但仅在没有其他线程创建/丢弃对象的受控条件下。
【讨论】:
【参考方案19】:只需使用 java 可视化 VM。
它拥有分析和调试内存问题所需的一切。
它还有一个 OQL(对象查询语言)控制台,可让您做许多有用的事情,其中之一是 sizeof(o)
【讨论】:
【参考方案20】:long heapSizeBefore = Runtime.getRuntime().totalMemory();
// Code for object construction
...
long heapSizeAfter = Runtime.getRuntime().totalMemory();
long size = heapSizeAfter - heapSizeBefore;
size 为您提供由于创建对象而增加的 jvm 内存使用量,这通常是对象的大小。
【讨论】:
如果在对象构造的代码期间GC在中间运行怎么办?现在可能一直产生正确的结果。【参考方案21】:我的答案基于 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;
我已经用原始类型、字符串和一些琐碎的类测试了这个解决方案。也可能有未涵盖的情况。
更新:修改示例以支持数组对象的内存占用计算。
【讨论】:
【参考方案22】:这个答案与对象大小无关,而是当您使用数组来容纳对象时;它将为对象分配多少内存。
因此,所有这些集合的数组、列表或映射都不会真正存储对象(仅在基元时,需要真正的对象内存大小),它只会存储这些对象的引用。
现在是Used heap memory = sizeOfObj + sizeOfRef (* 4 bytes) in collection
原语
int [] intArray = new int [1]; will require 4 bytes.
long [] longArray = new long [1]; will require 8 bytes.
对象
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.
我的意思是说所有的对象 REFERENCE 只需要 4 个字节的内存。它可能是字符串引用或双对象引用,但取决于对象创建所需的内存会有所不同。
例如)如果我为下面的类 ReferenceMemoryTest
创建对象,则将创建 4 + 4 + 4 = 12 字节的内存。当您尝试初始化引用时,内存可能会有所不同。
class ReferenceMemoryTest
public String refStr;
public Object refObj;
public Double refDoub;
所以在创建对象/引用数组时,它的所有内容都将被 NULL 引用占据。而且我们知道每个引用需要 4 个字节。
最后,下面代码的内存分配是 20 字节。
ReferenceMemoryTest ref1 = new ReferenceMemoryTest(); ( 4(ref1) + 12 = 16 字节) ReferenceMemoryTest ref2 = ref1; ( 4(ref2) + 16 = 20 字节)
【讨论】:
一个 4 字节的整数和一个未知大小的对象引用如何适合 4 个字节? @EJP 我的意思是说所有对象 REFERENCE 只需要 4 个字节的内存。它可能是字符串引用或双对象引用,但取决于对象创建所需的内存会有所不同。【参考方案23】:您可以生成堆转储(例如使用 jmap),然后分析输出以查找对象大小。这是一个离线解决方案,但您可以检查浅尺寸和深尺寸等。
【讨论】:
【参考方案24】:假设我声明了一个名为 Complex
的类,例如:
public class Complex
private final long real;
private final long imaginary;
// omitted
为了查看为此类的活动实例分配了多少内存:
$ jmap -histo:live <pid> | grep Complex
num #instances #bytes class name (module)
-------------------------------------------------------
327: 1 32 Complex
【讨论】:
【参考方案25】:对于 JSONObject,下面的代码可以帮助你。
`JSONObject.toString().getBytes("UTF-8").length`
以字节为单位返回大小
我通过将 JSONArray 对象写入文件来检查它。它给出了对象的大小。
【讨论】:
这仅适用于主要是字符串的对象。【参考方案26】:我怀疑您是否想以编程方式执行此操作,除非您只想执行一次并将其存储以供将来使用。这是一件代价高昂的事情。 Java 中没有 sizeof() 运算符,即使有,它也只会计算对其他对象的引用的成本和原语的大小。
您可以这样做的一种方法是将事物序列化为文件并查看文件的大小,如下所示:
Serializable myObject;
ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("obj.ser"));
oos.write (myObject);
oos.close ();
当然,这假设每个对象都是不同的,并且不包含对其他任何东西的非瞬态引用。
另一种策略是获取每个对象并通过反射检查其成员并将大小相加(布尔值和字节 = 1 个字节,短值和字符 = 2 个字节等),沿着成员资格层次结构向下工作。但这既乏味又昂贵,最终会做与序列化策略相同的事情。
【讨论】:
我会使用 ByteArrayOutputStream 将其序列化为 byte[]。这比将其写入文件要快得多。 @KorayTugay 确定对象的字节大小已经是一项代价高昂的操作。将每个对象写入磁盘以确定大小,只会让它爬行...... 序列化的对象格式与堆内存中对象的格式完全不同。最值得注意的是,对象类(及其所有可序列化的超类)的描述符被写入流中。因此,编写java.lang.Integer
的简单实例会产生大约 80 个字节,其中堆表示通常为 32(与对象流表示不同,堆表示取决于指针大小和对象对齐方式)。相比之下,序列化的null
引用需要一个字节,而不是堆内存中的四个或八个字节。以上是关于在 Java 中,确定对象大小的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章