我有一个应用程序,读取一个CSV文件与成堆的数据行。我根据数据类型向用户提供了行数的摘要,但我想确保不会读入太多行数据而导致OutOfMemoryErrors。每一行转换成一个对象。有没有一种简单的方法以编程方式找出该对象的大小?是否有一个引用定义了一个VM的基本类型和对象引用有多大?
现在,我有代码说读取多达32,000行,但我还希望有代码说读取尽可能多的行,直到我使用了32MB的内存。也许这是另一个问题,但我还是想知道。
我有一个应用程序,读取一个CSV文件与成堆的数据行。我根据数据类型向用户提供了行数的摘要,但我想确保不会读入太多行数据而导致OutOfMemoryErrors。每一行转换成一个对象。有没有一种简单的方法以编程方式找出该对象的大小?是否有一个引用定义了一个VM的基本类型和对象引用有多大?
现在,我有代码说读取多达32,000行,但我还希望有代码说读取尽可能多的行,直到我使用了32MB的内存。也许这是另一个问题,但我还是想知道。
当前回答
假设我声明了一个名为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
其他回答
可以使用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);
}
}
将以下内容添加到您的清单中。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
我的答案是基于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;
}
}
我已经用基本类型String和一些普通类测试了这个解决方案。可能也有不包括在内的情况。
更新:示例修改为支持数组对象的内存占用计算。
您必须使用工具来测量它,或者手工估计它,这取决于您正在使用的JVM。
每个对象都有一些固定的开销。它是jvm特有的,但我通常估计有40个字节。然后你要看看这个班级的成员。对象引用在32位(64位)JVM中是4(8)个字节。基本类型是:
布尔值和字节:1字节 Char和short: 2字节 Int和float: 4字节 Long和double: 8字节
数组也遵循同样的规则;也就是说,它是一个对象引用,因此在对象中占用4(或8)个字节,然后它的长度乘以其元素的大小。
试图通过调用Runtime.freeMemory()以编程方式来实现这一点并不能提供很高的准确性,因为对垃圾收集器的异步调用等等。使用-Xrunhprof或其他工具对堆进行分析将为您提供最准确的结果。
我偶然发现了一个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
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() { }
}