我有一个应用程序,读取一个CSV文件与成堆的数据行。我根据数据类型向用户提供了行数的摘要,但我想确保不会读入太多行数据而导致OutOfMemoryErrors。每一行转换成一个对象。有没有一种简单的方法以编程方式找出该对象的大小?是否有一个引用定义了一个VM的基本类型和对象引用有多大?
现在,我有代码说读取多达32,000行,但我还希望有代码说读取尽可能多的行,直到我使用了32MB的内存。也许这是另一个问题,但我还是想知道。
我有一个应用程序,读取一个CSV文件与成堆的数据行。我根据数据类型向用户提供了行数的摘要,但我想确保不会读入太多行数据而导致OutOfMemoryErrors。每一行转换成一个对象。有没有一种简单的方法以编程方式找出该对象的大小?是否有一个引用定义了一个VM的基本类型和对象引用有多大?
现在,我有代码说读取多达32,000行,但我还希望有代码说读取尽可能多的行,直到我使用了32MB的内存。也许这是另一个问题,但我还是想知道。
当前回答
你必须利用反射在物体上行走。做的时候要小心:
仅仅分配一个对象在JVM中就有一些开销。这个量因JVM而异,所以可以将此值作为参数。至少让它成为一个常量(8字节?),并应用于分配的任何东西。 仅仅因为一个字节理论上是1个字节,并不意味着内存中只需要一个字节。 在对象引用中会有循环,因此您需要使用object-equals作为比较器来保留HashMap或类似的东西,以消除无限循环。
@jodonnell:我喜欢你的解决方案的简单性,但许多对象是不可序列化的(所以这将抛出一个异常),字段可以是暂时的,对象可以覆盖标准方法。
其他回答
如果你只是想知道在你的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();
edit:我认为这可能会有帮助,因为问题作者还表示,他希望有处理“读取尽可能多的行,直到我使用了32MB内存”的逻辑。
我的答案是基于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或其他工具对堆进行分析将为您提供最准确的结果。
我曾经写过一个快速测试来进行评估:
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
考虑到对齐行为和可能的堆块报头开销,这正是我们所期望的。
仪器仪表方法详细在这里接受的答案是最准确的。我描述的方法是准确的,但只有在受控条件下,即没有其他线程创建/丢弃对象。
不需要干扰插装等,如果你不需要知道一个对象的确切字节大小,你可以使用以下方法:
System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
do your job here
System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
通过这种方式,您可以读取之前和之后使用的内存,并在获得使用的内存之前调用GC,将“噪声”降低到几乎为0。
为了得到更可靠的结果,您可以运行作业n次,然后将使用的内存除以n,得到一次运行占用的内存。甚至,你可以把整个过程运行更多次,得到一个平均值。