【问题标题】:Why does this code sample produce a memory leak?为什么此代码示例会产生内存泄漏?
【发布时间】:2010-05-16 12:54:17
【问题描述】:

在大学里,我们收到了以下代码示例,并被告知运行此代码时存在内存泄漏。该示例应该证明这是垃圾收集器无法工作的情况。

就我的面向对象编程而言,唯一能够造成内存泄漏的代码线是

items=Arrays.copyOf(items,2 * size+1); 

documentation 表示元素被复制。这是否意味着引用被复制(因此堆上的另一个条目被创建)或对象本身被复制?据我所知, Object 并因此 Object[] 被实现为引用类型。因此,为“项目”分配一个新值将允许垃圾收集器发现旧的“项目”不再被引用,因此可以被收集。

在我看来,这个代码示例不会产生内存泄漏。有人能证明我错了吗? =)

import java.util.Arrays;
public class Foo  
{  
private Object[] items;  
private int size=0;  
private static final int ISIZE=10;

public Foo()  
{  
  items= new Object[ISIZE];  
}  

public void push(final Object o){  
  checkSize();  
  items[size++]=o;  
}  

public Object pop(){  
  if (size==0)  
    throw new ///...  
  return items[--size];  
}  
private void checkSize(){  
  if (items.length==size){  
    items=Arrays.copyOf(items,2 * size+1);  
  }  
}  
}

【问题讨论】:

  • 你问的问题是正确的:代码是否真的泄漏内存?
  • 只要 Foo 实例没有被垃圾回收,你只是浪费内存。如果 Foo 实例的寿命很长,则它接近于“真正的”内存泄漏。但无论是否真的泄漏,代码都是糟糕的并且浪费了不必要的内存。
  • @Petar:对糟糕的代码表示赞同,这些都是你必须经历的一些奇怪的扭曲才能做出无法访问的引用。这个例子太人为了,似乎没有什么指导意义。
  • 这是 Stack 的新实现吗?代码类似于我的教授用于重新实现数据结构的示例。

标签: java memory-leaks garbage-collection


【解决方案1】:

pop 方法产生内存泄漏。

原因是您只是减少了队列中的项目数,但实际上并没有将它们从队列中删除。引用保留在数组中。如果您不删除它们,垃圾收集器将不会破坏对象,即使生成对象的代码已执行。

想象一下:

{
    Object o = new Object();
    myQueue.add(o);
}

现在您对该对象只有一个引用 - 数组中的那个。

稍后再做:

{
    myQueue.pop();
}

此弹出不会删除引用。如果您不删除引用,垃圾收集器会认为您仍在考虑使用此引用并且此对象很有用。

因此,如果您使用 n 对象数量填充队列,那么您将持有这些 n 对象的引用。

这是你的老师告诉你的内存泄漏

【讨论】:

  • 如果堆栈中的一个元素被覆盖,它可以被 GCed。所以从技术上讲,在某些情况下,它有泄漏,但从理论上讲,它没有。
  • 即使它被覆盖而不是被覆盖的那个也会是内存泄漏:) 不要忘记这一点。
【解决方案2】:

提示:泄漏在pop 方法中。考虑一下对弹出对象的引用会发生什么...

【讨论】:

  • 这取决于 copyOf 方法的确切作用。如果它只复制引用没有问题。如果它真的“复制”了这些项目,则 pop 方法返回的 Object 在执行 copyOf 后仍然引用可能不在新数组中的 Object。我看对了吗?
  • @citronas - Array.copyOf 仅将源数组中的引用复制到目标数组。它不会在源数组中创建对象的副本。
  • 此外,pop() 不调用 copyOf,因此它的行为甚至与我的“提示”没有直接关系。
【解决方案3】:

这里存在内存泄漏先验不是真的。

我认为教授认为您不会将弹出的项目清空(换句话说,在您返回 items[--size] 之后,您可能应该设置 items[size] = null)。但是当 Foo 实例超出范围时,所有内容都会被收集。所以这是一个很弱的练习。

【讨论】:

  • 设置 items[size] = null 将导致销毁返回的对象。如果我弹出一个项目,然后将其设为 null,我会得到 null 作为刚刚操作的项目。我看错了什么?
  • 他的意思是这样的:“对象结果 = items[size - 1]; items[size - 1] = null; size--; return result;”。 “结果”变量仍然指向对象。
【解决方案4】:

Joshua BlochEffective Java 中讨论了此示例。泄漏是在弹出元素时。引用一直指向您不使用的对象。

【讨论】:

  • 这个答案应该有剧透提醒!!
【解决方案5】:

代码示例不会产生泄漏。确实,当您调用 pop() 时,内存并没有为适当的对象释放 - 但它会在您下次调用 push() 时释放。

确实,示例永远不会释放内存。但是,未释放的内存总是被重新使用。在这种情况下,它并不真正符合内存泄漏的定义。

for(int i = 0; i < 1000; i++)
    foo.push(new Object());
for(int i = 0; i < 1000; i++)
    foo.pop();

这将产生未释放的内存。但是,如果您再次运行循环,或者十亿次,您将不会产生更多未释放的内存。因此,内存永远不会泄漏。

您实际上可以在许多 malloc 和 free (C) 实现中看到这种行为 - 当您释放内存时,它实际上并没有返回给操作系统,而是添加到一个列表中,以便在您下次调用 malloc 时返回。但我们仍然不建议 free 泄漏内存。

【讨论】:

    【解决方案6】:

    内存泄漏被定义为由持续执行导致的分配无限制增长。

    提供的解释解释了对象如何在弹出后通过堆栈中的引用继续保持活动状态,并且肯定会导致各种不当行为(例如,当调用者释放他们认为是最后一个引用并期望完成和内存恢复),但几乎不能称为泄漏。

    由于堆栈用于存储其他对象引用,以前的孤立对象将变得真正不可访问并返回到内存池。

    您最初的怀疑是正确的。提供的代码将提供有限的内存使用增长,并收敛到长期状态。

    【讨论】:

      【解决方案7】:

      提示:想象一下,如果您使用 Foo 对象,将 10000 个“重”项插入其中,然后使用 pop() 将它们全部删除,因为您的程序中不再需要它们,会发生什么情况。

      【讨论】:

        【解决方案8】:

        我不会直截了当地给你答案,但是看看 push(Object o) 做了什么 pop() 没有做。

        【讨论】:

          【解决方案9】:

          在 pop() 方法中,尺寸上的项目(即 items[size-1])未设置为 NULL。结果,仍然存在从对象 items 到 items[size-1] 的引用,尽管 size 已减一。在 GC 期间,即使没有其他对象指向 items[size-1] 也不会被收集,从而导致内存泄漏。

          【讨论】:

            【解决方案10】:

            考虑这个演示:

            Foo f = new Foo();
            {
                Object o1 = new Object();
                Object o2 = new Object();
                f.push(o1);
                f.push(o2);
            }
            
            f.pop();
            f.pop();
            
            // #1. o1 and o2 still are refered in f.items, thus not deleted
            
            f = null;
            
            // #2. o1 and o2 will be deleted now
            

            Foo 中应该改进几处,这将解决这个问题:

            1. pop 中,您应该将items 条目设置为null
            2. 你应该引入与checkSize相反的东西,比如shrinkSize,这将使数组更小(可能与checkSize类似)。

            【讨论】:

              猜你喜欢
              • 2011-01-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-10-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多