【问题标题】:Does referring to an object makes different in memory usage引用一个对象是否会导致内存使用不同
【发布时间】:2018-08-22 20:54:18
【问题描述】:

我有这两个代码:

A a = new A();
B b = a.getB();

B b = new A().getB();

在第一个代码中,我创建了两个对象并保存它们,因此程序需要内存来存储两个对象。
但是在第二个代码中,我从 A 创建了对象,我 没有 将它保存在任何变量中,所以我的问题是:
这两个代码有什么不同?它只是一种语法还是对记忆有影响?

【问题讨论】:

  • 略有不同,a 只能在超出范围后被垃圾回收;但是在第二种情况下仍然为 A 分配了内存,你只是没有办法再次引用它(A 的范围是语句;所以它可以在语句完成后进行 GC )。
  • 但真的:不要担心差异。使用你认为最易读的那个。
  • 这里的所有答案(至少到目前为止)都是错误的一件事:对象生命周期与范围无关。一旦不再有任何代码路径可以与该对象交互,该对象就会变得不可访问。当引用对象的变量仍在范围内时,可能会发生这种情况。一旦对象变得不可访问,它就有资格进行 GC。它可能会立即被回收(如果 JIT 对其进行优化),或者它可能会在将来的某个时间被回收。如果一个足够聪明的 JIT 可以内联您在对象上调用的所有方法,它甚至可以完全忽略分配。

标签: java oop memory-management


【解决方案1】:

引用/指针与对象

您的术语“已保存”没有技术含义,可能会让您感到困惑。

让我们变得更具体。将 A 类替换为 Cat 类。将 B 类替换为 Breed 类。

假设在实例化Cat 时,我们还实例化了该Cat 对象中的Breed 对象。换句话说,在这个例子中,我们在做lazy-loading。所以在实例化Cat 时,我们将Breed 的实例传递给构造函数。

两行

Cat a 变量不包含 Cat 对象,它包含对在内存中其他位置构造的 Cat 对象的引用(指针)。 Cat 对象在内存中的位置,基本上是一个数字,是 Cat a 所持有的。

Cat a = new Cat( "Fluffy" , new Breed( "Tabby" ) ) ;  Passing new instance of `Breed` to constructor of `Cat`, with resulting `Cat` object’s location in memory being returned to the `a` reference/pointer variable.

你可以把那行代码想象成这样:

Pointer-holding-memory-location-of-object-of-class-Cat a = Instantiate-a-new-Cat-object-somewhere-in-memory-and-return-its-memory-location( … ) ;

概念图:

请注意,访问Breed 对象的唯一方法是通过Cat 对象Fluffy。我们的代码可以访问该Cat 对象的唯一方法是通过名为a 的引用/指针变量。

Breed b = a.getBreed() ;  // Accessing the `Cat` instance, then traversing to the `Breed` object.

在这段代码之后,我们现在分配了一点内存来保存另一个指针/引用,这个变量名为bb 变量既不是Cat 也不是Breed,它是在内存中其他位置找到Breed 对象的内存位置。我们现在可以直接访问Breed 对象,而无需通过Cat 实例。

概念图:

一行

您的其他代码:

B b = new A().getB() ;

...转换为我们的示例:

Breed b = new Cat( "Fluffy" , new Breed( "Tabby" ) ).getBreed() ;

...非常相似,但从未将引用a 建立为命名变量。参考生成的,至少在概念上是这样(实际上JVM可能有优化)。简短的临时引用用于立即调用getBreed 方法。

在获得Breed 对象的内存位置后,对新Cat 对象的临时引用被释放,新Cat 对象超出范围。从技术上讲,新的Cat 对象可能仍会在内存中浮动片刻,成为garbage-collection 的候选对象,因为它会从内存中清除。

当然,上面看到的使用CatBreed 的新单行代码变得很傻:我们实例化一个新的Breed,将它传递给Cat 的构造函数,然后立即要求取回它。为了这个内存管理的讨论,让我们忽略尴尬的愚蠢。

概念图:

至于内存管理,在两种情况下可能会占用相同数量的内存(取决于您的 JVM 实现)。这两种情况都是建立一对对象和一对引用。一个细微的区别是,在您的第二种情况下,Cat 对象及其临时指针立即超出范围,并成为垃圾收集的候选对象。因此,内存可能会比您的第一个场景更快地被回收。在实践中,差异可能是微不足道的。

不要微优化

正如其他人评论的那样,所有这些都只是学术性的。您通常不应该担心这种微优化。

现代编译器和 JVM 的优化非常好,是有史以来优化和测试最好的大型软件项目。所以编写Java代码的最好方法就是简单。为人类编写简单易读和易于理解的代码,您还将编写最有可能被编译器和 JVM 优化的代码。具有讽刺意味的是,试图通过超越编译器和 JVM 来变得聪明可能会适得其反,从而导致代码的优化程度较低。

【讨论】:

    【解决方案2】:

    对于轻的物体,差别很小。
    在第一个代码中,A 对象被分配给一个变量。所以只有当变量超出范围时才有资格被垃圾收集。
    在第二个代码中,A 对象未分配给变量。所以它现在有资格被垃圾收集。
    一般来说,您不应该担心这种事情,并且只有在有意义的情况下才使用变量或不使用它。

    【讨论】:

    • 除非对象 B 具有对对象 A 的内部引用,这是完全可能的。另外,谁知道对象 A 中发生了什么。可能有一些静态列表,所有 A 的实例都添加到其中。
    • @aglassman 是的,我上面的 cmets 不精确。您也许可以说“可能符合条件”。
    【解决方案3】:

    你有点回答了你自己的问题。在第一个示例中,您创建了两个变量“A”和“B”,因此使用了更多内存。在第二个示例中,您仅在变量 B 上创建,因此使用的内存较少。在第二个示例中,创建了一个新的“A”,然后新对象调用 getB() 方法并将结果存储在变量“B”中。在这种情况下,内存差异非常小,但如果在程序过程中重复多次,那么内存使用量可能会累积。

    此外,Java 有自己的垃圾收集器,当它运行时,它会收集/删除不再可访问的对象的内存。一旦变量超出范围,垃圾收集就可以运行并清理未使用的内存。 有了车库收集器,两条语句中的内存差异几乎就无关紧要了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-06-28
      • 2015-09-14
      • 1970-01-01
      • 1970-01-01
      • 2014-06-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多