【发布时间】:2013-01-01 00:51:20
【问题描述】:
我正在编写一些需要在内存中保存大量数据的“大数据”软件。我用 C++ 写了一个原型,效果很好。然而,实际的最终用户通常使用 Java 编写代码,因此他们要求我也编写一个 Java 原型。
我已经完成了关于 java 内存占用的背景阅读和一些初步测试。例如,假设我有这个对象
public class DataPoint{
int cents, time, product_id, store_id;
public DataPoint(int cents, int time, int product_id, int store_id){
this.cents = cents;
this.time = time;
this.product_id = product_id;
this.store_id = store_id;
}
}
在 C++ 中,这个结构的大小是 16 字节,这是有道理的。在 Java 中,我们必须是间接的。如果我创建例如 10m 这些对象并在之前使用 Runtime.totalMemory() - Runtime.freeMemory() 然后根据需要进行划分,每个结构大约得到 36 个字节。大约 2.4 倍的内存差异非常糟糕;当我们试图在内存中保存数亿个数据点时,它会变得很丑。
我在某处读到,在 Java 中的这种情况下,最好将数据存储为数组——本质上是基于列的存储而不是基于行的存储。我想我明白这一点:基于列的方式减少了引用的数量,也许JVM甚至可以智能地将int打包成8字节的单词。
我可以使用哪些其他技巧来减少本质上是一个内存块的内存占用量,该内存块具有一个非常大的维度(数百万/十亿个数据点)和一个非常小的维度(O(1) 列数/变量)?
结果将数据存储为 4 个 int 数组,每个条目正好使用 16 个字节。教训:小对象在 java 中具有令人讨厌的比例开销。
【问题讨论】:
-
totalMemory 也包括空闲内存,尝试用 totalMemory()-freeMemory() 测量
-
首先,如果您想要一个真正准确的数字,请获取您的应用程序的堆转储并使用 Memory Analyzer (eclipse.org/mat) 之类的工具打开该转储以获得准确的数字。第二 - 对这些数据的访问模式是什么?也许您可以通过使用一些可以将未使用部分分页到磁盘的缓存库来获得更多空间?说将您的点分成 10K 项的数组并将这些“块”存储在 infinispan (jboss.org/infinispan) 或类似的?
-
@Henry 好点!使用 totalMemory()-freeMemory() 测量得出每个结构 36 字节的值。优于 52,但仍然是 C++ 的 2 倍以上。
-
@radai 访问模式是一次将所有数据保存在内存中,这样我们就可以运行简化的 SQL 类型的查询,这会使普通的 SQL 引擎爆炸。
-
那么是的,尽可能地放弃 OO 伪装并使用 4 个巨大的 int 数组肯定会为您节省一些空间 - 假设您预先分配所有空间并且不必处理调整大小和碎片化。再加上享元模式,您甚至可以尝试假装它的 OO :-) 另外,如果您要使用数组,您是否可以确保在任何给定时刻加载的产品和商店少于 2^16 个,并使用更小的原语对于 store_id 和 product_id ?你可以有一个从较短的 temp_id 到真正的 32 位的查找表。
标签: java memory optimization