从数据库加载26MB文本数据消耗了258MB的JVM堆

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

因此,实际上,从数据库中加载26MB的文本消耗了216MB(从256-40),这些数据在内存不足消耗190MB ? 也许这将是框架,但我不知道它如何比正在加载的实际数据消耗更多……

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

有些事情需要考虑:

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

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

hibernate会从SQL结果集中取值并将其映射到java对象上。 它可能需要将结果集的内容包装/转换为您在映射中定义的类型。

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

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

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

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

然后,如果它分成许多小块,700,000个小字符串而不是1,000个非常长的字符串,那么数据结构开销的大小是实际数据大小的三倍是完全合理的,可以轻松地推动你超过200MB。

各种各样的事情。

让我们考虑一下你的行有10个文本列,它们表示为一个带有10个字符串字段的简单Java Bean。

String有4个字段:char []和3个int。

String是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,字符串本身为8 + 4 + 4 + 4,实际数据为8 + 4 + 20,或62字节。

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

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

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

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