【问题标题】:Memory Efficiency in JavaJava 中的内存效率
【发布时间】:2015-04-08 04:05:54
【问题描述】:

关于内存使用和变量实例化哪个更好或没有区别:

这个

for(int i = 0; i < someValue; i++)
{
    Obj foo = new Obj();
    Use foo.....
}

相对于:

Obj foo;

for(int i = 0; i < someValue; i++)
{
    foo = new Obj();
    Use foo.....
}

【问题讨论】:

  • 第一个版本更好,因为它可能会缩短对象的生命周期。
  • 在内存使用方面没有区别。不要再重视这些微小的细节,而要着眼于大局!
  • 不要试图做编译器的工作。
  • @Cicada:这是一个完全合理的问题
  • @nosid 绝对不是真的。即使假设 javac 确实生成了不同的字节码,JIT 也会进行活性分析。

标签: java optimization memory jvm


【解决方案1】:

没有区别。编译器将优化内存使用方面的任何潜在差异。

如果你编译(使用javap -c)这两个例子并比较字节码,你会发现字节码是一样的。当然,这取决于 JVM 版本。但由于这个示例非常简单,因此可以肯定地假设两者的内存效率都不比另一个高。


示例 1:

代码:

public class example1 {
    public static void main(String[] args) {
        for (int i=0; i<10; i++) {
            Object a = new Object();
        }
    }
}

字节码:

public class example1 extends java.lang.Object{
public example1();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   bipush  10
   5:   if_icmpge       22
   8:   new     #2; //class java/lang/Object
   11:  dup
   12:  invokespecial   #1; //Method java/lang/Object."<init>":()V
   15:  astore_2
   16:  iinc    1, 1
   19:  goto    2
   22:  return
}

示例 2:

代码:

public class example2 {
    public static void main(String[] args) {
        Object a;
        for (int i=0; i<10; i++) {
            a = new Object();
        }
    }
}

字节码:

public class example2 extends java.lang.Object{
public example2();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_2
   2:   iload_2
   3:   bipush  10
   5:   if_icmpge       22
   8:   new     #2; //class java/lang/Object
   11:  dup
   12:  invokespecial   #1; //Method java/lang/Object."<init>":()V
   15:  astore_1
   16:  iinc    2, 1
   19:  goto    2
   22:  return
}

【讨论】:

  • 现在这是快速思考 - 荣誉
  • 我更喜欢第二个(它在循环之外声明变量),以便调试时我会看到变量的当前值,并且它不会在每个变量上变得不可见和可见循环迭代。
  • Joshua Bloch(Effective Java 的作者)会提出不同的观点,如第 45 条:最小化局部变量的范围中所述。 :)
【解决方案2】:

在现代 JVM 中,两者的工作方式相同,编译器优化也会使两者相同。

忽略编译器优化和现代 JVM,第一种方法更好。

【讨论】:

  • 您错误地比较了这两种情况。顺便说一句,第一种方法更具可读性(没有不必要的对象范围)。
【解决方案3】:

正如其他答案所指出的那样,您的示例之间没有区别。但有趣的问题是:如果你的例子很慢,你应该怎么做?

如果您想在性能非常关键的部分减少分配/垃圾收集所花费的时间,请考虑重用对象,而不是在每次迭代时分配新对象。

foo = new Obj();
for(int i = 0; i < someValue; i++)
{
    foo.init(i);
    Use foo.....
}

来自java performance tuning(一本旧书,但在现代 jvm:s 和 .NET clr 中也是如此)

...对象的创建成本很高。在哪里 重用同一个对象是合理的,你应该这样做。你需要 要注意什么时候不要调用新的。一种相当明显的情况是 当您已经使用过一个对象并且可以在使用之前将其丢弃时 即将创建同一类的另一个对象。你应该看看 对象并考虑是否可以重置字段和 然后重用该对象,而不是将其丢弃并创建另一个。 这对于不断变化的对象尤其重要 使用和丢弃

【讨论】:

  • 重用对象会导致更稀有,但因此非常昂贵的 GC 运行 - 收集年轻对象非常便宜。通常这在现代 JVM 上实际上是适得其反的(Hotspot 的人通常不推荐它)
  • 在上面的例子中,为什么收藏会更贵?我更习惯于 CLR,在那里我经常受到分配的限制,特别是因为在默认 GC 模式下,如果另一个 GC 触发了另一个线程,则无法在一个线程上进行分配。
  • 您可能想阅读世代 GC。是的,CLR 在并行 GC 算法方面仍然相当糟糕,但同样的原则也适用于那里:许多极短、廉价的年轻一代 GC 比少数极其昂贵的老一代 GC 更好。早逝的年轻对象是现代 JVM 最需要优化的。
  • 我熟悉世代 gc:s 和不同的成本。我想说的是,在某些情况下,您甚至无法在每次迭代中分配一个 gen0 对象,无论感觉多么便宜。我的示例通常适用于具有许多线程的场景,如果允许它们在每次迭代中分配,这些线程将快速分配到 Gen0 限制。例如,查看 .NET Workstation GC 如何处理该问题:一旦触发 GC,所有线程都会为 GC 暂停。像这样的序列化执行比 GC 和分配本身要昂贵得多。
  • 再次,主要在典型情况下讨论分配,而不是GC。另外:就像我说的,只分析代码,除非有真正的性能优势,否则不要优化。
【解决方案4】:

第一个示例在对象范围方面最有意义,但两者都应该与另一个一样具有内存效率。

【讨论】:

    【解决方案5】:

    编译器优化将使它们两者相同。但是,如果您要忽略编译器优化,我更喜欢第一种方法。更重要的是,甚至 Joshua Bloch 也建议在 Effective Java 中这样做(一本好书)。

    【讨论】:

      【解决方案6】:

      编译器应该为你优化这个。我更喜欢第一个而不是第二个,因为它更具可读性。让Object foo 具有更大的范围可能会造成混淆。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-07
        • 1970-01-01
        • 1970-01-01
        • 2016-10-28
        • 2013-04-03
        • 1970-01-01
        • 2017-11-01
        • 1970-01-01
        相关资源
        最近更新 更多