我有一个应用程序可以读取包含大量数据行的 CSV 文件。我根据数据的类型给用户总结了行数,但是我想确保我不会读入太多的数据行而导致OutOfMemoryError
。每行转换为一个对象。有没有一种简单的方法可以以编程方式找出该对象的大小?是否有定义 VM
的原始类型和对象引用有多大的参考?
现在,我的代码说读取多达 32,000 行,但我还希望代码说读取尽可能多的行,直到我使用 32MB 内存。也许这是一个不同的问题,但我仍然想知道。
您可以使用 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
您应该使用 jol,它是作为 OpenJDK 项目的一部分开发的工具。
JOL(Java Object Layout)是用于分析 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 和垃圾收集器内部结构。
vmDetails
现在是 VM.current().details()
。
GraphLayout.parseInstance(instance).toFootprint()
我发现了解对象大小更有用
我无意中发现了一个 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
仅在 HotSpot VM 上受支持
几年前 Javaworld 有 an article on determining the size of composite and potentially nested Java objects,他们基本上是在 Java 中创建 sizeof() 实现。该方法基本上建立在其他工作的基础上,人们通过实验确定基元和典型 Java 对象的大小,然后将这些知识应用于递归遍历对象图以计算总大小的方法。
仅仅因为类的幕后发生的事情,它总是比原生 C 实现更不准确,但它应该是一个很好的指标。
或者,一个名为 sizeof 的 SourceForge 项目提供了一个具有 sizeof() 实现的 Java5 库。
PS 不要使用序列化的方式,序列化对象的大小和它存活时消耗的内存量之间没有关联。
首先,“对象的大小”在 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 大小初始化为合理的值,则代码会更有效率。
如果您只想知道 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 内存之前读取尽可能多的行”的逻辑。
当我在 Twitter 工作时,我编写了一个用于计算深度对象大小的实用程序。它考虑了不同的内存模型(32 位、压缩 oops、64 位)、填充、子类填充,可在循环数据结构和数组上正常工作。你可以只编译这个 .java 文件;它没有外部依赖:
许多其他答案提供了浅大小 - 例如没有任何键或值的 HashMap 的大小,这可能不是您想要的。
jamm 项目使用上面的 java.lang.instrumentation 包,但会遍历树,因此可以为您提供深度内存使用。
new MemoryMeter().measureDeep(myHashMap);
https://github.com/jbellis/jamm
要使用 MemoryMeter,请使用“-javaagent:/jamm.jar”启动 JVM
使用 JetBrains IntelliJ 时,首先在文件 | 中启用“附加内存代理”。设置 |构建、执行、部署 |调试器。
https://i.stack.imgur.com/TvMel.png
Calculate Retained Size
。即使在选项中检查 Attach memory agent
之后。是否包含在 AS 中?我在 Intellij docs 中找到了它。
您必须使用反射来行走对象。这样做时要小心:
仅仅分配一个对象在 JVM 中有一些开销。数量因 JVM 而异,因此您可以将此值作为参数。至少让它成为一个常量(8 个字节?)并应用于任何分配的东西。
仅仅因为 byte 理论上是 1 字节并不意味着它只需要一个内存。
对象引用中会有循环,因此您需要保留一个 HashMap 或类似的对象,使用 object-equals 作为比较器来消除无限循环。
@jodonnell:我喜欢您的解决方案的简单性,但是许多对象不是可序列化的(因此这会引发异常),字段可以是瞬态的,并且对象可以覆盖标准方法。
您必须使用工具来测量它,或者手动估计它,这取决于您使用的 JVM。
每个对象有一些固定的开销。它是特定于 JVM 的,但我通常估计 40 个字节。然后你必须看看班级的成员。对象引用是 32 位(64 位)JVM 中的 4 (8) 个字节。原始类型是:
布尔值和字节:1 个字节
char 和 short:2 个字节
整数和浮点数:4 个字节
long 和 double:8 个字节
数组遵循相同的规则;也就是说,它是一个对象引用,因此在您的对象中占用 4(或 8)个字节,然后将其长度乘以其元素的大小。
由于对垃圾收集器的异步调用等,尝试通过调用 Runtime.freeMemory()
以编程方式执行此操作不会给您带来太多的准确性。使用 -Xrunhprof 或其他工具分析堆将为您提供最准确的结果。
boolean[]
内 1 字节。实际上所有非双/长类型的原语都是 4 个字节。后者是 8(答案也错误地将它们设为 4)
还有 Memory Measurer 工具(以前在 Google Code,现在在 GitHub),它很简单,在商业友好的 Apache 2.0 许可 下发布,如similar question 中讨论过。
如果您想测量内存字节消耗,它也需要 java 解释器的命令行参数,但其他方面似乎工作得很好,至少在我使用它的场景中。
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() { }
}
无需弄乱仪器等,如果您不需要知道对象的字节精确大小,您可以使用以下方法:
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。
我正在寻找满足以下要求的对象大小的运行时计算:
在运行时可用,无需包含检测。
与 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;
}
}
这是我使用一些链接示例制作的实用程序,用于处理带有压缩 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 );
}
}
Instrumentation
因为我没有启动 tomcat,ObjectSizeCalculator
因为不确定 VM 类型(HotSpot)和 JOL
bacouse spring beans。我使用它并添加第二个参数来忽略单例即 AbstractRefreshableApplicationContext.getBeanFactory().getSingletonMutex()
并重构 internalSizeOf
代码以忽略 Class 和 Enum
如果那是您所要求的,则没有方法调用。通过一些研究,我想你可以编写自己的。特定实例具有从引用和原始值的数量以及实例簿记数据派生的固定大小。您只需遍历对象图。行类型的变化越少,就越容易。
如果这太慢或只是比它的价值更多的麻烦,总是有很好的老式行计数经验法则。
我写了一个快速测试来即时估计:
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
考虑到对齐行为和可能的堆块头开销,这是我们所期望的。
此处接受的答案中详述的检测方法最准确。我描述的方法是准确的,但仅在没有其他线程创建/丢弃对象的受控条件下。
只需使用 java 可视 VM。
它拥有分析和调试内存问题所需的一切。
它还有一个 OQL(对象查询语言)控制台,可让您做许多有用的事情,其中之一是 sizeof(o)
一个可能的 2022 年答案。
https://github.com/ehcache/sizeof
https://mvnrepository.com/artifact/org.ehcache/sizeof
https://mvnrepository.com/artifact/org.ehcache/sizeof/0.4.0
0.4.0 版只有一个(编译)依赖于
https://mvnrepository.com/artifact/org.slf4j/slf4j-api
这是一件好事。
示例代码:
//import org.ehcache.sizeof.SizeOf;
SizeOf sizeOf = SizeOf.newInstance(); // (1)
long shallowSize = sizeOf.sizeOf(someObject); // (2)
long deepSize = sizeOf.deepSizeOf(someObject); // (3)
long heapSizeBefore = Runtime.getRuntime().totalMemory();
// Code for object construction
...
long heapSizeAfter = Runtime.getRuntime().totalMemory();
long size = heapSizeAfter - heapSizeBefore;
size 为您提供由于创建对象而增加的 jvm 内存使用量,这通常是对象的大小。
我的回答基于尼克提供的代码。该代码测量序列化对象占用的字节总数。所以这实际上测量了序列化的东西 + 普通对象的内存占用(只是序列化例如 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;
}
}
我已经用原始类型、字符串和一些琐碎的类测试了这个解决方案。也可能有未涵盖的情况。
更新:修改示例以支持数组对象的内存占用计算。
这个答案与对象大小无关,而是当您使用数组来容纳对象时;它将为对象分配多少内存大小。
因此,所有这些集合的数组、列表或映射都不会真正存储对象(仅在基元时,需要真正的对象内存大小),它只会存储这些对象的引用。
现在 Used heap memory = sizeOfObj + sizeOfRef (* 4 bytes) in collection
(4/8 字节)取决于(32/64 位)操作系统
原语
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 字节)
您可以生成堆转储(例如使用 jmap),然后分析输出以查找对象大小。这是一个离线解决方案,但您可以检查浅尺寸和深尺寸等。
假设我声明了一个名为 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
如果您的应用程序将 Apache commons lang library 作为依赖项或正在使用 Spring framework,那么您还可以使用 SerializationUtils
类快速找出任何给定对象的近似字节大小。
byte[] data = SerializationUtils.serialize(user);
System.out.println("Approximate object size in bytes " + data.length);
对于 JSONObject,以下代码可以帮助您。
`JSONObject.toString().getBytes("UTF-8").length`
返回大小(以字节为单位)
我通过将 JSONArray 对象写入文件来检查它。它给出了对象的大小。
我怀疑您是否想以编程方式执行此操作,除非您只想执行一次并将其存储以备将来使用。这是一件代价高昂的事情。 Java 中没有 sizeof() 运算符,即使有,它也只会计算对其他对象的引用的成本和原语的大小。
您可以这样做的一种方法是将事物序列化为文件并查看文件的大小,如下所示:
Serializable myObject;
ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("obj.ser"));
oos.write (myObject);
oos.close ();
当然,这假设每个对象都是不同的,并且不包含对其他任何东西的非瞬态引用。
另一种策略是获取每个对象并通过反射检查其成员并将大小相加(布尔值和字节 = 1 个字节,短值和字符 = 2 个字节等),沿着成员资格层次结构向下工作。但这既乏味又昂贵,最终会做与序列化策略相同的事情。
java.lang.Integer
实例会产生大约 80 个字节,其中堆表示通常是 32(与对象流表示不同,堆表示取决于指针大小和对象对齐)。相反,序列化的 null
引用需要一个字节,而不是堆内存中的四个或八个字节。
byte[0]
、byte[1]
、byte[5]
、int[0]
、int[1]
、int[2]
的大小是多少?如果结果包括数组长度和内存对齐的开销,那就太好了。