Java程序可能非常耗费内存。例如,Double
对象有24个字节:8个字节的数据和16个字节的JVM强加的开销。通常,表示基元类型的对象非常昂贵。
Java标准库中的任何集合都会发生同样的情况。甚至有一些违反直觉的事实,例如HashSet
比HashMap
更耗尽内存,因为HashSet
内部包含HashMap
(http://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html)。
在高性能设置中建模数据和对象委托时,您能否提出一些建议,以便这些"弱点" Java的缓解了吗?
答案 0 :(得分:3)
取决于申请,但一般来说
基元(并行)数组中的布局数据结构
尝试做大"扁平"对象,内联其他合理的子结构
专门化原语集合
重用对象,使用对象池,ThreadLocals
去堆外
我不能说这些做法是最好的",因为它们不幸地让你受苦,失去了使用Java的原因,降低了灵活性,可支持性,可靠性,可测试性等等。好"代码库的属性。
但是,他们当然可以降低内存占用和GC压力。
答案 1 :(得分:3)
Java中容易忽略的一个内存问题是内存泄漏。尼古拉斯·格林已经向你指出了记忆分析。
许多人认为Java的垃圾收集可以防止内存泄漏,但事实并非如此 - 所需要的只是一个被遗忘的参考资料,以永久保持对象。矛盾的是,尝试优化程序可能会为内存泄漏带来更多机会,因为最终会出现更复杂的数据结构。
如果要实现堆栈,则会出现内存泄漏的一个示例:
Integer stack[];
stack = new Integer[10];
int stackPtr = 0;
// a few push operation on our stack.
stack[stackPtr++] = new Integer(5);
stack[stackPtr++] = new Integer(3);
// and pop from the stack again
--stackPtr;
--stackPtr;
// at this point, the stack is logically empty, but
// the Integer objects are still referenced by the array,
// and are basically leaked.
正确的解决方案是:
stack[--stackPtr] = null;
答案 2 :(得分:3)
我用来减少记忆的一些技巧:
new String
处理旧的大字符串。array[x|y<<4]
表示16xN数组。StringBuilder
,并选择初始容量,以防止在典型情况下进行内部重新分配。
StringBuilder
而不是字符串连接,因为已编译的类文件使用new StringBuilder()
而没有初始容量来连接字符串。答案 3 :(得分:2)
如果您有高性能约束并且需要将集合用于简单类型,那么您可以查看Java的Primitive Collections的一些实现。
有些是:
此外,作为参考,请看一下这个问题:Why can Java Collections not directly store Primitives types?
答案 4 :(得分:1)
LuísBianchin已经为您提供了一些在Java中实现最佳集合的库。 尽管如此,您似乎特别关注Java集合的内存分配。在这种情况下,有一些选择很简单。
您可以使用缓存来限制集合(缓存)可以分配的内存。通过这样做,您只需在主内存中加载最常用的条目,而不需要从磁盘/网络/任何内容加载整个数据集。我强烈推荐 Guava Cache ,因为它有很好的文档记录,非常成熟。
有时缓存不是解决您问题的方法。例如,在ETL解决方案中,您可能知道只会加载每个条目一次。对于这种情况,我建议使用持久性集合。这些是磁盘存储集合,它们比传统数据库更快,但具有很好的Java API。 MapDB 和 PCollections 对我来说是最好的图书馆。
最重要的是,如果您真的想知道程序内存分配的实际状态,我强烈建议您使用分析器。这样,您不仅可以了解集合占用的内存量,还可以了解GC的行为方式。
事实上,如果存在实际的内存问题,您应该只尝试替代Java的集合和数据结构,这是分析器可以告诉您的。
JDK有一个名为VisualVM的分析器,它做得很好。不过,如果你负担得起,我建议你使用商业分析器。与VisualVM相比,商业分析器通常对应用程序的性能影响很小。
最后,它与你的问题没有严格的关系,但它是密切相关的。如果您想将Java对象序列化为最佳二进制表示,我建议您使用 Java中的Google协议缓冲区。协议缓冲区非常适合传输数据结构,认为网络使用尽可能少的带宽并具有非常快速的编码/解码。
答案 5 :(得分:1)
你可以做很多事情。
以下是一些问题和解决方案:
在java中更改字符串的值时,实际上不会覆盖该字符串。而是创建一个新字符串来替换旧字符串。但是,旧字符串仍然存在。当有效使用RAM时,这可能是一个问题。以下是此问题的一些解决方案:
诸如fileWriters和fileReaders之类的Writer和reader对象也会占用RAM。如果你有很多,这也可能导致问题。以下是一些解决方案:
java中的每个对象都占用内存。当你有一个你将不再使用的物体时,保持它不是很方便。
答案 6 :(得分:-1)
谨防早期优化。 见When is optimisation premature?
虽然不知道应用程序或运行时环境的确切要求,但根据我的经验,java能够处理我抛出的任何内容。如果性能或垃圾收集(标记内存泄漏)是一个问题,那么对您的演示/概念验证应用程序进行一些分析可能会花费很长时间。