为什么此代码示例会产生内存泄漏?

时间:2010-05-16 12:54:18

标签: java memory-leaks garbage-collection

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

就我的面向对象编程而言,唯一能够创建内存泄漏的代码行将是

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);  
  }  
}  
}

10 个答案:

答案 0 :(得分:11)

pop方法产生内存泄漏。

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

想象一下:

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

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

稍后你会这样做:

{
    myQueue.pop();
}

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

因此,如果您使用n个对象填充队列,那么您将保留对这些n个对象的引用。

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

答案 1 :(得分:9)

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

答案 2 :(得分:6)

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

我认为教授考虑到你没有将弹出的项目归零(换句话说,在你返回items[--size]之后,你可能应该设置items[size] = null)。但是当Foo实例超出范围时,一切都将被收集。所以这是一个相当弱的练习。

答案 3 :(得分:4)

Effective Java Joshua Bloch讨论了此示例。漏洞是弹出元素的时候。引用指向您不使用的对象。

答案 4 :(得分:4)

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

所提供的解释解释了弹出后如何通过堆栈中的引用继续保持活动状态,并且肯定会导致各种不当行为(例如,当调用者发布他们认为是最后一个引用的对象并期望完成时,记忆恢复),但很难被称为泄漏。

由于堆栈用于存储其他对象引用,因此先前的孤立对象将变为真正无法访问并返回到内存池。

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

答案 5 :(得分:4)

代码示例不会产生泄漏。确实,当你调用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时要返回的列表中。但我们仍然不建议免费泄漏内存。

答案 6 :(得分:1)

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

答案 7 :(得分:1)

我不打算给你答案,但看看pop()没有做什么推(对象o)。

答案 8 :(得分:1)

在pop()方法中,大小上的项目(即items [size-1])未设置为NULL。因此,仍然存在从对象 items 到items [size-1]的引用,尽管大小已经减少了一个。在GC期间,即使没有其他对象指向它,也不会收集[size-1]项,这会导致内存泄漏。

答案 9 :(得分:1)

考虑一下这个演示:

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类似)。