4

应用程序(Spring、JPA Hibernate、Sybase 12、Webapp)在启动时在本地运行时会消耗基于 VisualVM 的 256MB 堆空间中的 40MB。当我触发返回 70,000+ 行(文本数据无 blob)的搜索时,堆空间图会飙升至 256MB 并耗尽内存。我已经通过使用 setMaxResults(limit) 解决了这个问题。但是,当我查询相同的数据,复制粘贴到文本文件并保存到文件系统时,我可以看到大小只有 26MB 的文本。

所以实际上,从数据库加载 26MB 的文本量消耗了 216MB(从 256-40),在内存不足发生时谁在消耗 190MB?也许它会是框架,但我看不出它如何消耗比正在加载的实际数据更多......

* *再次注意,我用 setMaxResults(limit) 解决了这个问题,我的问题不是做什么,而是为什么,出于教育目的。

4

2 回答 2

5

需要考虑的一些事项:

您的操作系统可能使用每个字符 8 位的编码来存储文本文件。Java 字符串在内部都是以每个字符 16 位编码的,那里的空间是原来的两倍。

只有几位数字的数字将比数字编码为文本更小。例如,'1' 是文本文件中的一个字节字符,但值为 1 的 long 是内存大小的八倍。

hibernate 从 SQL 结果集中取出值并将其映射到您的 java 对象上会有重复。它可能需要将结果集的内容包装/翻译成您在映射中定义的类型。

如果您的每实体数据实际上很小且实体数量众多,那么对象开销大小与数据大小的比率显然会很高。

如果集合中有少量数据,则集合的大小可能会相对于数据快速增加。在极端的示例中,如果您有一个或两个字符串的 LinkedList,那么每 16-32 位实际数据的指针仅消耗 192 位。在数组列表中,指向 16-32 位数据的指针仍然是 64 位。(当然假设是 64 位操作系统。)

您在休眠中加载的每个对象都被“跟踪”以在所谓的 L1 缓存中进行脏检查。对于具有少量数据的大量实体而言,相对于数据大小而言,用于执行此操作的内部数据结构和仪器确实存在相当多的开销。

--

所以26MB的数据在java中已经是52MB的内存数据了,假设都是字符串,没有数字,没有日期,否则会更大。

然后如果它被分成许多小块,700,000 个小字符串而不是 1,000 个非常长的字符串,数据结构开销的大小是实际数据大小的三倍是完全合理的,很容易让你超过 200MB。

于 2012-07-27T03:33:10.613 回答
2

各种各样的事情。

例如,假设您的行有 10 个文本列,它们表示为具有 10 个字符串字段的简单 Java Bean。

一个字符串有 4 个字段:一个 char[] 和 3 个整数。

String 是 Object 的后代,Object 有 1 个 int 和对其类的引用。

在 64 位 JVM 上,这些引用很可能是 8 个字节(但不一定,但为了争论,我们会坚持使用它)。

一个 10 个字符的字符串将有一个 char[10] 和 3 个 int,每个 4 个字节。

char[10] 是指向数组的指针。一个数组必须跟踪它的长度,这可能是另外 4 个字节,它也是一个 Object(因此是类指针和另一个 int)加上数据。但是 Java 中的字符在内部表示为 UTF-16,每个字符 2 个字节。因此,10 个字符的实际数组需要 24 个字节。对该数组的引用是一个指针。

因此,单个 String 实例是:8 + 4 用于 Object,8 + 4 + 4 + 4 用于 String 本身,8 + 4 + 20 用于实际数据,即 62 个字节。

你的 bean 有 10 个字符串字段,加上扩展对象,所以 8 + 4 + (10 * 8)。

因此,对于 100 个字符的文本,数据库中的一行是 8 + 4 + (10 * 8) + (10 * 62) 等于 712 个字节。

这些不是完美的数字,我无法具体说明数组是如何存储的,并且对象引用在 64b JVM 上可能不是 8 个字节。

但它让您对所涉及的开销有所了解。这仅适用于您的原始数据。如果您将这些行存储在 ArrayList 中,那么有 70,000 * 8 只是指向您的对象 - 560K 仅用于结构。

于 2012-07-27T03:45:55.277 回答