构建内存高效的Java应用程序有哪些最佳实践?

时间:2015-02-19 20:08:57

标签: java performance memory-leaks

Java程序可能非常耗费内存。例如,Double对象有24个字节:8个字节的数据和16个字节的JVM强​​加的开销。通常,表示基元类型的对象非常昂贵。

Java标准库中的任何集合都会发生同样的情况。甚至有一些违反直觉的事实,例如HashSetHashMap更耗尽内存,因为HashSet内部包含HashMaphttp://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html)。

在高性能设置中建模数据和对象委托时,您能否提出一些建议,以便这些"弱点" Java的缓解了吗?

7 个答案:

答案 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)

我用来减少记忆的一些技巧:

  • 制作自己的IntArrayList(etc)类,防止装箱
  • 创建自己的IntHashMap(etc)类,其中键是基元
  • 使用nio的ByteBuffer有效地存储大型数据(以及在本机内存中,堆外)。它类似于一个字节数组,但包含在任意偏移量下从缓冲区存储/检索所有基本类型的方法(交易记忆的速度)
  • 不要使用池,因为池会将未使用的实例显式保持活动状态。
  • 几乎不使用线程,他们需要超级内存(在本机内存中,在堆外)
  • 在制作大字符串的子字符串并丢弃原始字符串时,子字符串仍然是指原始字符串。因此,请使用new String处理旧的大字符串。
  • 线性数组小于多维数组,如果除最后一个维之外的所有数据的大小都是2的幂,则计算索引最快: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集合的内存分配。在这种情况下,有一些选择很简单。

  1. 高速缓存
  2. 您可以使用缓存来限制集合(缓存)可以分配的内存。通过这样做,您只需在主内存中加载最常用的条目,而不需要从磁盘/网络/任何内容加载整个数据集。我强烈推荐 Guava Cache ,因为它有很好的文档记录,非常成熟。

    1. 持久收藏
    2. 有时缓存不是解决您问题的方法。例如,在ETL解决方案中,您可能知道只会加载每个条目一次。对于这种情况,我建议使用持久性集合。这些是磁盘存储集合,它们比传统数据库更快,但具有很好的Java API。 MapDB PCollections 对我来说是最好的图书馆。

      1. 个人资料内存使用情况
      2. 最重要的是,如果您真的想知道程序内存分配的实际状态,我强烈建议您使用分析器。这样,您不仅可以了解集合占用的内存量,还可以了解GC的行为方式。

        事实上,如果存在实际的内存问题,您应该只尝试替代Java的集合和数据结构,这是分析器可以告诉您的。

        JDK有一个名为VisualVM的分析器,它做得很好。不过,如果你负担得起,我建议你使用商业分析器。与VisualVM相比,商业分析器通常对应用程序的性能影响很小。

        1. 内存最佳数据很适合网络。
        2. 最后,它与你的问题没有严格的关系,但它是密切相关的。如果您想将Java对象序列化为最佳二进制表示,我建议您使用 Java中的Google协议缓冲区。协议缓冲区非常适合传输数据结构,认为网络使用尽可能少的带宽并具有非常快速的编码/解码。

答案 5 :(得分:1)

你可以做很多事情。

以下是一些问题和解决方案:

  1. 在java中更改字符串的值时,实际上不会覆盖该字符串。而是创建一个新字符串来替换旧字符串。但是,旧字符串仍然存在。当有效使用RAM时,这可能是一个问题。以下是此问题的一些解决方案:

    • 当使用字符串指定某个对象的“状态”或其他只能具有特定可能值集的内容时,请不要使用字符串。而是使用枚举。如果您还不知道枚举是什么或如何使用枚举,here's a link to a tutorial on what enums are and how to use them!
    • 如果您使用字符串作为变量,其值将在程序中的某个点发生变化,请不要像往常那样定义字符串。而是使用java.lang包中的StringBuilder类。 StringBuilder是一个用于创建字符串并更改其值的类。此类处理字符串的方式与平常不同。当它用于更改字符串的值时,StringBuilder不会创建具有不同值的重复字符串来替换旧字符串,它实际上会更改原始字符串的值。因此,由于您没有创建重复的字符串,因此可以节省RAM。 Here is a link to to the StringBuilder class in the java api.
  2. 诸如fileWriters和fileReaders之类的Writer和reader对象也会占用RAM。如果你有很多,这也可能导致问题。以下是一些解决方案:

    • 所有reader和writer对象都有一个名为close()的方法。正如你可能猜到的那样,它会关闭作者或读者对象。它所做的就是摆脱读者或作家的对象。每当你有一个阅读器或编写器对象,当你知道你将永远不再使用阅读器或编写器对象时,你就可以达到代码中的要点,请使用这种方法。它将摆脱读者或作者对象,并释放一些RAM。
  3. java中的每个对象都占用内存。当你有一个你将不再使用的物体时,保持它不是很方便。

    • Object类有一个名为finalize()的方法。此方法与reader和writer对象中的close()方法具有相同的效果。当你不再使用一个对象时,使用finalize()方法去除它并释放一些RAM。

答案 6 :(得分:-1)

谨防早期优化。 见When is optimisation premature?

虽然不知道应用程序或运行时环境的确切要求,但根据我的经验,java能够处理我抛出的任何内容。如果性能或垃圾收集(标记内存泄漏)是一个问题,那么对您的演示/概念验证应用程序进行一些分析可能会花费很长时间。