java - 在Java , 确定对象的大小最好的办法是什么?

  显示原文与译文双语对照的内容

比如说,假设我有一个可以在CSV文件中读取数据行的应用程序。 我给用户一个基于数据类型的行的摘要,但是我想确保我不会读入太多的数据行并导致 OutOfMemoryError 。 每行转换成一个对象。 是否有一个简单的方法来以编程方式找出该对象的大小? 是否有一个引用定义了 VM的大基元类型和对象引用?

现在,我已经编码告诉我们阅读,以 32, 000行,但我还想向有编码告诉我们读取尽可能多的行,直到我用过褴褛的内存。 也许这是个不同的问题,但我还是想知道。

时间:

你可以使用 java.lang.instrument 软件包

编译并将该类放入一个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

对一些几年前 Javaworld java,已经确定制作的尺寸大小和可能嵌套一篇关于Java对象,它们基本上简单介绍如何创建一个 sizeof() 进行了较详细的讨论 这种方法基本上是建立在其他工作在那里人们通过实验确定了大小的基本类型和典型Java对象,然后将该知识应用于一个方法,该方法递归地散步即可对的总大小的一个对象图。

它总是比原生的C 实现稍微精确一些,因为在类的幕后发生了事情,但它应该是一个好的指示器。

或者一个被适当称为 sizeof的SourceForge项目,它提供了一个Java5库,带有 sizeof() 实现。

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

首先,"对象的大小"在Java中不是一个定义良好的概念。 你可以代表对象本身及只是它的引用它的成员,该对象及其所有对象到( 引用图形) 。 你可以表示内存中的大小或者磁盘上的大小。 JVM可以优化诸如字符串之类的东西。

所以唯一正确的方法是询问 JVM,这是一个好的探查器( 我使用 YourKit ),它可能不是你想要的。

从上述描述听起来好像在大多数jvms,但是,将是自包含的,而不是每一行都有一个大的版本也可能会好approximation.依存树,因此序列化方法 执行以下操作的最简单方法如下:


 Serializable ser;
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(baos);
 oos.writeObject(ser);
 oos.close();
 return baos.size();

在内存中,但它是一个很好的与普通引用这将不会approximation,请记住,如果你有对象序列化并不能保证匹配 size. 给出了正确的结果,和大小。 这些代码可能会有少许更有效如果你 initialise 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();

编辑:我认为这可能有帮助的问题作者也说他想有逻辑,以处理"尽可能多的行,直到我使用 32 MB的内存。"

你必须使用反射来遍历对象。 请务必小心:

  • 仅仅分配一个对象在JVM中有一些开销。 金额因JVM而异,因此你可以将该值作为参数。 至少是一个常量( 8字节) 并应用于任何分配的。
  • byte 理论上是 1字节并不意味着它只占用内存中的一个。
  • 会有环路能在对象引用,所以,你要跟踪一个 HashMap,somesuch作为比较器使用 object-equals 以消除死循环。

@ jodonnell: 我喜欢你的解决方案的简单性,但是许多对象不是可以序列化的( 这样会引发一个异常),字段可以是瞬态的,并且对象可以覆盖标准方法。

iPhone 7 还没出来,我们已经在iPhone上获取细节 8,或者不管是想到下一步。 java.lang.instrument.Instrumentation 类提供了一个获取Java对象大小的好方法,但它要求你定义一个 premain 并使用Java代理运行你的程序。 当你不需要任何代理,然后你必须为应用程序提供一个虚拟的Jar代理时,这就非常无聊了。

所以我得到了一个使用 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你是using,你必须测量它以一个工具,或者预计,它的手,并这要看了。

每个对象有一些固定的开销。 是 JVM-specific,但我通常估计 40字节。 然后,你必须查看类的成员。 在一个 32位 ( 64位 ) jvm,对象引用是否 4 ( 8 ) bytes. 基元类型包括:

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

遵循同样的规则,也就是说,数组是一个对象引用,以便以 4 ( 或者 8 ) 字节在你的对象,然后它的长度乘以它的元素的大小。

如果尝试这样做,它以编程方式通过调用 Runtime.freeMemory() 来只是不能给你很多的准确性,因为异步调用到垃圾回收器,与 -Xrunhprof 等等 分析堆或者其他工具会给你最准确的结果。

相类似问题,另外还有存量 Measurer 工具,它是简单且所出版的书里 commercial-friendly 2.0许可证重整计划,如讨论了

如果你想测量内存字节消耗,那么它也需要一个command-line参数到java解释器,否则似乎可以正常工作,至少在我使用的场景中。

我写了一次快速测试来估算一下:


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(), 钥匙,运行请求垃圾收集并与 waits. 上面的输出是:


nested: 160000 each=16
static nested: 160000 each=16

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

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

...