clear方法的源码如下,在代码里他循环了所有item,将其previous和next全部设置成了null,目的何在?
[code="java"]
public void clear() {
Entry e = header.next;
while (e != header) {
Entry next = e.next;
e.next = e.previous = null;
e.element = null;
e = next;
}
header.next = header.previous = header;
size = 0;
modCount++;
}
[/code]
如果改成如下实现会有什么影响呢?其他的item都已经访问不到了,应该也不会影响GC啊。
public void clear() {
header.next = header.previous = header;
size = 0;
modCount++;
}
[/code]
看了楼主的图之后我觉得自己之前搞错了,先向楼主和看客道个歉。
但是这也并非楼主说的加快GC回收那么简单。
为了这个我便仔细地去读了一下LinkedList的源代码,原来发现惑起Iterator。
[code="java"]
private class ListItr implements ListIterator {
private Entry lastReturned = header;
private Entry next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
if (index < (size >> 1)) {
next = header.next;
for (nextIndex=0; nextIndex<index; nextIndex++)
next = next.next;
} else {
next = header;
for (nextIndex=size; nextIndex>index; nextIndex--)
next = next.previous;
}
}
public boolean hasNext() {
return nextIndex != size;
}
public E next() {
checkForComodification();
if (nextIndex == size)
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.element;
}
......
}
[/code]
如源代码所示,如果程序员对该List对象使用过iterator,并且迭代到中间就断了,那么栈内就会有一个引用指向Iterator对象,而Iterator对象中又有lastReturned 引用指向一个Entry再结合上图就不难理解整条链得不到释放,GC不能回收的疑惑了。
郑重地向楼主道歉,之前我真向简单了点。
不过该观点估计也不一定对,有鉴于之前的幼稚的想法。
关键词:垃圾回收
我猜的是不是和内存有关啊,设为null后,垃圾处理器应该会回收对象的吧。
这样做更安全,避免对象间存在相互引用而无法回收。
List add()的对象可能在list外还有引用,有可能是可达的。
Entry(a)-->Entry(b)
对象a,b都被引用了,而Entry(a).next=Entry(b),
Entry(b)被Entry(a)引用,如果在对象a,b又有相互引用,懂了吧
你如果只是用一部分的话,你只是清空了LinkedList对于里面所含有对象指针的释放,也就是说LinkedList不再拥有这些对象的引用了,我清空了他们,就是说我有一批苹果,我不吃了,我全部倒掉了,但是问题是:苹果呢?他们虽然被我倒掉了,但他们不会无源无故地消失吧,,,,除非这个是哈里波特的魔法世界,虽然这个我也不反对,但JVM可不这么想的吧,所以呢,这些对象并不会消亡,依然存在,可能在同时这些对象在外面还有别的对象在引用着,所以只能做多一步,就是让他们的引用全部都是null,当引用与堆间的联系没有了的话,这样才会使堆的东西孤零零的,一般在这个时候堆就会感慨:世事万千,以前一起看月亮的时候叫人家小甜甜,现在新人胜旧人,叫人家“牛夫人”,,,然后堆就会想到去死了。。。JVM这时候其实也是做好事啦,他就只好做顺水人情,当一下坏人,把堆给干掉了。。。唉。。。这种故事一般都是悲剧结尾的吧。。。苦了这些痴情的人儿啊。。。但是需要正视的问题是:LinkedList只是把自己中每个对象的引用设为null没错,那些不再被引用的对象是会被回收了,但是如果有一些对象外部还在继续用的话,也是回收不了的。举例:
String s = new String("hello");
String s2 = s;
LinkedList list = new LinkedList();
list.add(s);
list.clear();
System.out.println(s2);
如果还能打印出一个东西来的话,证明clear还是不会让里面的所有对象实体都消失的。
[code="java"]
Entry e = header.next;
while (e != header) {
Entry next = e.next;
e.next = e.previous = null;
e.element = null;
e = next;
}
[/code]
上面这段代码的作用就是将链表中除header的每个元素的previous、next均置为null,这样做原因就是为了方便垃圾回收!
如果不这样做的话,当前链表的元素会被其他元素所引用,导致无法gc(存在循环引用)!
关于LinkedList的源码分析可参考[url]http://boy00fly.iteye.com/blog/1138904[/url]
[img]http://dl.iteye.com/upload/picture/pic/98779/8b7f52b1-2125-3afe-891b-3930499fb345.png[/img]
|
|
|
[img]http://dl.iteye.com/upload/picture/pic/98777/d1ea1d3b-8b2e-3fda-b213-3621107e5dd2.png[/img]
这样的话,楼主能够看出端倪了吗?
第二张图画错了,header的previous、next应该指向header本身。 :cry:
[quote]
谢谢你写了这么长得故事,可是还是没解答我的疑问,我的疑问是,即使不把它们一一设成NUll,这些Entry也与这个list没有引用关联了,即使这些entry之间互相关联,可对于GC来说,这些Entry都是不可达的,应该还是可以GC掉得。
[/quote]
如果不把entity的previous、next置为null,那么就有指向堆内存(如果是一个reference)中真实对象的引用,那么堆内存的这块区域的实际对象,从gc的原理来讲:他并非root不可触及的,所以不可回收。
next引用,引用并没有被销毁,不是所以gc 是不会回收的,仍然存在,感觉是这样。
如果list里的元素没有外部引用是可以的。也是等效的。
但是如果有一个元素被外部引用,那么对象的链还是可达的,导致list里的所以对象都可达而不会被JVM回收。
如果照jdk中设计的clear时把链拆开,那么就算外部有一个对list里某一元素的引用,也不会影响到其他的元素了,只影这一个元素。
list是个线性的链,实际对象的关系引用是一个立体图。
使用楼主的 代码
[quote]
[code="java"]
class A{
A a;
}
public class Test{
public void testMethod(){
A a1=new A();
A a2=new A();
a1.a=a2;
a2.a=a1;
}
}
[/code]
[/quote]
我进行了改写
[code="java"]
public void testMethod(){
A a1=new A();
A a2=new A();
a1.a=a2;
a2.a=a1;
// a1 = null;
// a2 = null;
System.gc();//调用GC
}
class A{
A a;
protected void finalize() {
System.out.println("垃圾回收了");
}
[/code]
如果如楼主所写,那么console 是不输出的,因为gc不会回收,虽然没有外部指向两个对象,但是他们的属性有引用存在。
如果去掉注释,也就是 使 a1,a2 为null 则会输出 “垃圾回收了” 说明gc调用了finalize方法,进行了回收。
所以 上面的 例子 每个 entry 为空才行。
[quote]这些怎么会是root可及的呢?应该不会有栈桢中的变量会引用到这些entry吧,也不会有静态变量引用过来,
关于GC roots,推荐你看下http://icyfenix.iteye.com/blog/802638这个博客,最近看到这个博客,感觉学到不少,也才会有现在的疑问。[/quote]
我们来分析下,为什么说这些entity[b]有可能[/b]是root可触及的呢?
首先Entity作为LinkedList的内部静态类,LinkedList的引用是存在对每个Entity对象的引用的,有可能你的LinkedList的对象只是作为局部变量使用,出了方法的栈帧就没用了,是没错,但是作为类库的笔者的话,他本是并不知道你的LinkedList对象是怎么使用的,如果你的LinkedList作为一个类变量存在的话会是什么样的情况呢? 只要你当前类没有卸载,你的LinkedList的对象就一直会存在,接着你的每个Entity指向的堆内存的数据就会一直存在着引用,所以说时root可触及的,clear方法中的while循环的作用就是为了防止类似这样的情况方法,确保无用的数据可以垃圾回收。
上次好像是我没说好,不好意思
我这样说吧 楼主看看 有说服力么
[code="java"]
public void testMethod(){
A a1=new A();
A a2=new A();
a1.a=a2;
a2.a=a1;
//a2.a= null;
a1 = null;
System.gc();
}
[/code]
class A 不变。
如果是这样的话,虽然销毁了a1 ,但是gc并不会回收a1,因为 a2.a=a1;引用的存在
这个地址仍然是在的。如果去掉了 注释,使a2.a= null; 这样a1 就可以被销毁了,因为所有关于它的引用都没了。
所以 clear方法就清除了 所有的引用,不然虽然list不再需要他们,但是之间的引用是存在的,所以需要那个while循环,来给他们置空。
还在讨论啊?
不管怎样,良好的编程习惯是必须的!
每段代码都应该做好自己的职责,特别是对于公用类。
如果你都不做好撤销引用,保证GC的工作;那调用者如果也没做好这部分工作,那么内存垃圾就会不断累积
[quote]我的意思是不一一设置成null,只是把header所指向的引用改下,这样不是照样在linkedlist对象里没有对剩下那些entry的引用了么?剩下那些entry不是应该还是可以被GC么?[/quote]
哦,我明白你的意思了,确实如你所说如果是根据根节点搜索算法判断对象是否可回收的话,确实没有必要了。可能这个也有历史或者其他的原因,在早期的时候有些JVM采用的是引用计数算法,采用这种算法的时候如果不做while循环的操作,就可能会有问题了。例如:LinkedList所存放的真实的堆内存对象必须在entity对象被回收后,才有可能被回收。
:D
[quote]呵呵,感觉不太像是因为历史原因,因为每个JDK的发布,都有配套的GC算法,应该不会现在的代码还用原来的GC吧…[/quote]
当然现有的代码没有用原来的GC,有可能是为了保持这种兼容性或者说写这个类的程序员太懒或者也没注意到,哈哈,纯粹猜想。坐等高人解释吧!
header.next = header.previous = header;
光这样怎么会有用呢?
这样的话header指针还指向原来的单元,对吧原来的单元还存着头结点中的对象,不是吗?
注意这句:
[color=red]e.next = e.previous = null; [/color]
因为LinkedList是双向循环链表。
执行
[color=cyan]header.next = header.previous = header;[/color]
之后,头节点是没有指向别的节点了,但是别的节点还指向它的上一个结点,并且别的节点也指向下一个节点,也就是说头结点下一个节点的next-->下一个节点,而头结点的下一个节点的pre-->header,这样头节点本身释放不了,header的下一个节点也释放不了,因为它的下一个节点的下一个节点的pre-->hreder的下一个节点。
以此类推,那块代码是不能去掉的,而且很关键。
给分吧楼主。
这样跟你说吧:
String a = new String("123456");
String b = new String("2356");
String c = new String("2356");
String d = new String("2356");
LinkedList ll = new LinkedList();
ll.add(a);
ll.add(b);
ll.add(c);
ll.add(d);
ll.clear();
如果没有那个循环就成了什么样子呢?
b有一个引用指向entry(b)中的值,entry(b)的pre->entry(a),entry(b)的next-->entry(c);如果不执行让他们指向null,a,b,c,d的引用任然指向这个list的元素,而list的元素之间也有相互指向,只要在a,b,c,d中存在一个没有被销毁,那么这一大块内存都会一直被占用,不晓得你现在会不会有点明白。
就好比一个对象里面有一个对象被一个该对象外部的引用持有,那么这个对象是绝对不能被回收的,如果这样还被回收,那java就写不出什么稳定的程序了。
你把邮箱给我吧,我画了个图,你看了估计就明白了,但是我现在在上班,因为是银行里面,什么都要权限,图片也不好传,就可以写邮件。
已经发了站内信给你了,你一看估计就明白了。
哈哈,这么简单的问题,弄了这半天,哎,累不累啊,你的精神值得我们学习,给点分吧!哈哈。
你看了我发给你的站内信还不能明白?
那我也没法儿讲得再明白了,堆栈图里面一目了然。
一般Entry的简单定义都是内部类的方式:
private class Entry{
private Entry pre;
private Entry next;
private Object data;
}
LinkedList的add方法把传入的参数写到data中,如果data的引用存在,并且Entry的pre、next又指向了别的Entry对象,你觉得这整条对象链能被回收吗?
实在弄不明白我也就木得办法咯。
[img]http://dl.iteye.com/upload/attachment/557035/bb1acd9d-0185-3fed-a6e9-33cb10899063.jpg[/img]
看一下这个对象的简单堆栈图吧,这是不使用循环语句,执行clear语句之后的情形。
其中栈里面a,b,c,d,指向的是entry里面的数据节点,entry上的箭头指的是pre和next引用.
再仔细看看源代码,和你改后的区别,源代码都是牛人写的一般,绝对安全可靠!
看看 回收策略,http://icyfenix.iteye.com/blog/802638,我刚看了下,还不错了,这个已经有人发给你了!
应该是垃圾回收的效率问题,当linkedlist存放大量元素时,调用clear()后,会使过期的元素数据最快被回收。因为gc总是先判断为null的引用对象,其次才是内存中的互相引用的对象构成的孤岛,如果这个孤岛很大,这样gc判断起来会很耗资源,效率也就降低了。
说简单点不明白,你接着头疼吧,那个对象中
有多个元素,你把那个对象中的元素之间的关系都摸掉了,按照你的意思是gc应该回收这个对象。可是偏偏没有。你就纠结在这里了,前面很多人回答都对了啊。你砸还纠结呢!!!
举个例子:你有一个衬衫,上面有8个扣子,假如他们都扣起来时作为一个对象。难道你把他们都从衬衫上扯下来,这些扣子就没用了吗?
还不明白,不要学习源代码了,对你来说是种折磨!!!
設置為null的對象,首先釋放了他所占用的內存空間,java垃圾回收機制中首先會回收這類對象。