Java 为什么此代码示例会产生内存泄漏?
在大学里,我们收到了下面的代码示例,我们被告知,在运行此代码时存在内存泄漏。该示例应证明这是一种垃圾收集器无法工作的情况 就我的面向对象编程而言,唯一能够产生内存泄漏的代码行是Java 为什么此代码示例会产生内存泄漏?,java,memory-leaks,garbage-collection,Java,Memory Leaks,Garbage Collection,在大学里,我们收到了下面的代码示例,我们被告知,在运行此代码时存在内存泄漏。该示例应证明这是一种垃圾收集器无法工作的情况 就我的面向对象编程而言,唯一能够产生内存泄漏的代码行是 items=Arrays.copyOf(items,2 * size+1); 系统表示元素已被复制。这是否意味着复制引用(从而创建堆上的另一个条目)或复制对象本身?据我所知,Object和Object[]是作为引用类型实现的。因此,为“items”分配一个新值将允许垃圾收集器发现旧的“item”不再被引用,因此可以被
items=Arrays.copyOf(items,2 * size+1);
系统表示元素已被复制。这是否意味着复制引用(从而创建堆上的另一个条目)或复制对象本身?据我所知,Object和Object[]是作为引用类型实现的。因此,为“items”分配一个新值将允许垃圾收集器发现旧的“item”不再被引用,因此可以被收集
在我看来,这个代码样本不会产生内存泄漏。有人能证明我错了吗
提示:泄漏在
pop
方法中。考虑一下对一个弹出对象的引用会发生什么… 这个例子在<代码>有效的java < /> >中讨论了<代码> Joshua Bloch < /C>。泄漏是在弹出元件时发生的。引用一直指向您不使用的对象。这里的内存泄漏不是先验的事实
我认为教授已经记住,您并没有将弹出的项目置零(换句话说,在返回
items[--size]
之后,您可能应该将items[size]=null
)。但当Foo实例超出范围时,所有内容都将被收集。因此,这是一个相当弱的练习。提示:想象一下,如果你使用一个Foo对象,在其中插入10000个“重”项,然后使用pop()删除所有这些项,因为你的程序不再需要它们。我不会直接给你答案,但看看push(对象o)做了什么,pop()做不了什么。在pop()中方法,则大小上的项(即,项[size-1])未设置为NULL。因此,尽管大小减少了1,但仍然存在从对象项到项[size-1]的引用。在GC期间,即使没有其他对象指向项[size-1],也不会收集项[size-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
for(int i = 0; i < 1000; i++)
foo.push(new Object());
for(int i = 0; i < 1000; i++)
foo.pop();
在Foo
中应改进以下几点,以解决此问题:
pop
中,您应该将项设置为null
checkSize
相反的内容,例如shrinkSize
,这将使数组更小(可能与checkSize
的方式类似)pop方法产生内存泄漏 原因是您只减少了队列中的项目数量,但实际上并没有将它们从队列中删除。引用仍保留在数组中。如果不删除它们,垃圾收集器将不会破坏对象,即使生成对象的代码已执行 想象一下:
{
Object o = new Object();
myQueue.add(o);
}
现在这个对象只有一个引用-数组中的引用
稍后你会:
{
myQueue.pop();
}
此pop不会删除引用。如果不删除引用,垃圾收集器会认为您仍在考虑使用该引用,并且该对象很有用
因此,如果您用n
数量的对象填充队列,那么您将保留这些n
对象的引用
这是您的老师告诉您的内存泄漏。内存泄漏被定义为由于正在执行而导致的分配无限增长 提供的解释解释了对象如何在弹出后通过堆栈中的引用继续保持活动状态,并且肯定会导致各种错误行为(例如,当调用方释放他们认为是最后一个引用并期望最终确定和内存恢复时),但很难称之为泄漏 当堆栈用于存储其他对象引用时,以前的孤立对象将变得真正不可访问,并返回到内存池
你最初的怀疑是正确的。提供的代码将提供有限的内存使用增长,并收敛到长期状态。代码示例不会产生泄漏。确实,当您调用pop()时,不会为相应的对象释放内存,但下次调用push()时会释放内存 确实,该示例从未释放内存。但是,未释放的内存始终被重新使用。在这种情况下,它并不真正符合内存泄漏的定义
for(int i = 0; i < 1000; i++)
foo.push(new Object());
for(int i = 0; i < 1000; i++)
foo.pop();
for(int i=0;i<1000;i++)
foo.push(新对象());
对于(int i=0;i<1000;i++)
foo.pop();
这将产生未释放的内存。但是,如果您再次运行循环,或者运行数亿次,您将不会产生更多未释放的内存。因此,内存永远不会泄漏
实际上,在许多malloc和free(C)实现中都可以看到这种行为——当您释放内存时,它实际上不会返回到操作系统,而是添加到下次调用malloc时返回的列表中。但是我们仍然不建议释放内存泄漏。这个答案应该有一个扰流板警告!!这取决于copyOf方法到底做了什么。如果它只复制引用,就没有问题。如果它真的“复制”了这些项,那么pop方法返回的对象仍然引用一个在copyOf执行之后可能不在新数组中的对象。我看到的是否正确?@citronas-
Array.copyOf
仅将源数组中的引用复制到目标数组。它不会在源数组中创建对象的副本。此外,pop()
不会调用copyOf
,因此其行为甚至与我的“提示”没有直接关系。设置items[size]=null将导致销毁返回的对象。如果我弹出一个项目,然后将其设置为null,那么我确实会得到null作为刚刚打开的项目。我怎么看错了?他有什么意思