2

我正在开发一个加载大量数据的应用程序(比如来自 csv)。

我正在创建List<List<SimpleCell>>并将读取的单元格加载到其中。SimpleCell 类包含 5 个 * String,每个String平均有 10 个字符。

所以我在想,如果我读取 1000 行 - 每行包含 160 列 - 给出 1000*160=160 000SimpleCell的实例 - 这将是大约 160 000 * sizeof(SimpleCell.class)=~ 160 000 * 10 * 5 = 8 000 000字节 =~ 7.63 MB。

但是当我查看 jconsole 时(并单击后Perform GC),内存使用量约为 790MB。这怎么可能?

请注意,我不存储对任何“临时”对象的任何引用。这是内存使用量上升时的代码:

        for(int i = r.getFromIndex(); i <= r.getToIndex(); ++i) {
            System.out.println("Processing: 'ZZ " + i + "'");
            List<SimpleCell> values = saxRead("ZT/ZZ " + i + "");
            rows.add(values);
        }

saxRead只是创建 inputStream 用 SAX 解析它,关闭流,然后返回单元格(由 SAXHandler 创建) - 所以只有局部变量(我认为在不久的'将来'会被丢弃)。

out of heap error在阅读 1000 行时得到,但我必须阅读大约 7k。

显然 - 关于 jvm 内存,我不知道一些事情。那么为什么在加载这么少量的数据时内存使用量如此之大呢?

4

4 回答 4

3

一个字符串使用 48 个字节加上文本的大小 * 2。(每个字符是 2 个字节)简单单元格对象使用 40 个字节,它们的列表使用 1064 个字节。

这意味着每行使用 1064 + 160 * 40 + 5 * 180 * (48 + 20) 字节或大约 68K。如果您有 1000 行,您将使用大约 70 MB,这比您看到的要少得多。

我建议您使用内存配置文件来查看究竟使用了多少内存。例如 VisualVM 或 YourKit。

根据您构建字符串的方式,您可以保留比这更多的内存。例如,您可能会保留对原始 XML 的引用,因为当您获取substring它时,您实际上持有的是原始 XML 的副本。


你可能会发现这个类很有用。如果它们使用的内存超过了它们的需要量,它将减少使用的内存量,并使用固定大小的缓存来减少重复。

static class StringCache {
    final WeakReference<String>[] strings;
    final int mask;

    @SuppressWarnings("unchecked")
    StringCache(int size) {
        int size2 = 128;
        while (size2 < size)
            size2 *= 2;
        strings = new WeakReference[size2];
        mask = size2 - 1;
    }

    public String intern(String text) {
        if (text.length() == 0) return "";

        int hash = text.hashCode() & mask;
        WeakReference<String> wrs = strings[hash];
        if (wrs != null) {
            String ret = wrs.get();
            if (text.equals(ret))
                return ret;
        }
        String ret = new String(text);
        strings[hash] = new WeakReference<String>(ret);
        return ret;
    }
}
于 2012-09-19T19:26:50.250 回答
2

JVM 内存管理引入了很多开销。例如,在 32 位 vm 上,一个包含 5 个字符的字符串会消耗 58 个字节的内存(不仅仅是 5 个!):

JVM 开销:16b + 簿记字段:12b + 指向 char[] 的指针:4b + char[] jvm 开销:16b + 数据:10b

于 2012-09-19T19:25:48.473 回答
2

使用 VisualVM 来分析您的堆使用情况,并准备好大吃一惊。

于 2012-09-19T19:26:44.403 回答
1

Java非常消耗内存。考虑这些估计:

32 位虚拟机:

您的字符串之一的大小(大约)

10 个 UTF-16 字符 = 20 个字节

1 个数组长度 = 4 个字节

1 个数组对象头 = 8 个字节

1 个数组引用 = 4 个字节

1 偏移量、计数、哈希码(内部字段)= 12 字节

1 个对象头 = 8 个字节

1 个典型的 Java 字符串 = 20 + 4 + 8 + 4 + 12 + 8 = 56 个字节

简单单元格的大小(大约,包括字符串)

5 个字符串 = 56 * 5 = 280 字节

5 个字符串引用 = 5 * 4 字节 = 20 字节

1 个对象头 = 8 个字节

1 个简单单元 = 180 + 20 + 8 = 308 字节

160000 简单单元 = 308 * 160000 = 49280000 字节

64 位 VM(无压缩 oops)

您的字符串之一的大小(大约)

10 个 UTF-16 字符 = 20 个字节

1 个数组长度 = 4 个字节

1 个数组对象头 = 8 个字节

1 个数组引用 = 8 个字节

1 偏移量、计数、哈希码(内部字段)= 12 字节

1 个对象头 = 8 个字节

1 个典型的 Java 字符串 = 20 + 4 + 8 + 8 + 12 + 8 = 60 字节

简单单元格的大小(大约,包括字符串)

5 个字符串 = 60 * 5 = 300 字节

5 个字符串引用 = 5 * 8 字节 = 40 字节

1 个对象头 = 8 个字节

1 个简单单元 = 300 + 40 + 8 = 308 字节

160000 简单单元 = 348 * 160000 = 55680000 字节

显然距离您的 790 Mb 很远(看起来像是泄漏),但几乎比您估计的要多一个数量级。

于 2012-09-19T19:54:29.880 回答