这只是Jdk1.7 java.util.LinkedList类的一个方法, 完整代码见 Jdk1.7
/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node f) {
final E element = f.item;
final Node next = f.next;
f.item = null;
f.next = null; // help GC 这一行的代码是什么作用?
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
我在研究Jdk源码时,注意到Jdk里面非常多地方会把引用置为null,然后标注help gc.
f.next = null很多人很自然的就认为是把next赋值为null了,然后JVM内存空间就释放了, 很多文档和网上的帖子都是这样介绍的, 这是完全错误的.
当然这样理解对平时的Java代码开发也没什么影响,因为平时大家都是在使用Jdk里面的类和方法,不需要自己编写.但是自定义集合类,特别是在自定义线程安全的并发集合时这块就很重要了.面向java核心功能的开发,需要对JVM的原理有一定的理解.
首先来了解一下jvm(java虚拟机)中的几个比较重要的内存区域:
java中内存的分配方式有两种,一种是在堆中分配,一种是在栈中分配,所有new出来的对象都是在堆中分配内存空间的。java中基本数据类型如int,float,double,char,byte等不是对象,除此之外一切都是对象。
C语言里的指针大家都知道,就是用来操作内存的. 其实Java中也有指针,
可以认为Java中的引用就是指针,是一种限制的指针,不能指向任意位置的内存,并且不需要显示回收对象,对象回收的工作由JVM自动完成。
String s = new String("test");
分为多个步骤:
1. 用java关键字new创建了一个String对象,Jvm会在堆内存中为其分配内存空间.
2. 在栈中创建了一个指向String对象的引用s
3. 栈中变量(不管是引用还是基本数据类型)的生命周期都是方法级别的, 上面提到了当方法调用完成时,栈帧消失,栈中的引用消失,根据可达性分析算法,在堆中分配的对象不可达就会被垃圾回收。
在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的.
那么哪些点可以作为GC Roots呢?一般来说,如下情况的对象可以作为GC Roots:
即使在可达性分析算法中不可达的对象,也不是马上就死的.要宣告一个对象死亡.至少要经历两次标记过程:如果对象在可达性分析后发现没有与GC ROOTS相连的引用链,那么他将会被第一次标记.并进行一次筛选.筛选的条件是此对象有没有必要执行finalize方法,如果对象没有覆盖finalize方法或者finalize方法已经被执行过了.虚拟机将视为”没有必要执行”,如果有必要执行finalize方法,那么这个对象会被放入F-Queue队列中,并在稍后由虚拟机创建一个低优先级的Finalizer线程去执行他,这里的执行时值虚拟机会触发这个方法,但不承诺会等待他运行结束.稍后会进行第二次小规模标记.被标记两次的对象基本上他就是真的被回收了.
package gc;
import java.io.IOException;
public class SystemGc {
static class Node {
String name; //节点名称
Node next; //指向下一个节点
Node(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable { //JVM GC时会调用finalize方法
System.out.println("I am removing by GC : " + this.name);
super.finalize();
}
}
private Node head;
public static void main(String[] args) throws IOException {
SystemGc systemGc = new SystemGc();
systemGc.removeHead();
System.gc();
System.in.read();
}
private void removeHead() {
Node a = new Node("node_a"); //新建两个node节点
Node b = new Node("node_b");
this.head = a; //head指向a
a.next = b; //a next执行b,形成一个链表
// a.next = null; //这一步完全可以不需要,a也会被回收!!
unlinkHead(); //其实链表头结点的删除动作,就是将head指向下一个节点,然后头结点不可到达,就会被GC
}
private void unlinkHead() {
Node first = this.head;
head = first.next; //head指向下一个节点.
}
}
执行结果: (VM options添加: -XX:+PrintGCDetails 打印GC日志)
[GC [PSYoungGen: 2601K->632K(75840K)] 2601K->632K(249280K), 0.0272617 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
[Full GC (System) [PSYoungGen: 632K->0K(75840K)] [ParOldGen: 0K->541K(173440K)] 632K->541K(249280K) [PSPermGen: 3210K->3208K(21248K)], 0.0222488 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
I am removing by GC : node_a
代码里重写了Node对象的finalize,用来检测对象什么时候被回收的.
可以看到head指向node_b之后, node_a没有引用指向它, 在下一次的GC中就会被回收.
a.next = null; 这一步完全可以不需要,a也会被回收. 这里我们是采用System.gc()方法触发GC. 实际环境中,gc触发的机制相对复杂些,会分为新生代/老年代/永久代,在为对象分配内存空间时,如果空间不足就会触发gc.
到这里开始提出的那个问题就可以解答清楚了: java中我们只能通过new关键字主动的创建对象,Jvm自动帮我们完成内存分配的工作. 对象内存空间回收的工作是由JVM GC完成的, 我们不能主动的回收对象, Jdk的置null的方法只是help GC帮助回收, f.next = null只是把next引用指向空,在下一次GC时,会由JVM回收堆内存.
平时开发中我们确实不需要把引用置为null,因为方法执行完毕, 栈中的引用也就释放了. 但是在一些特殊的场景下,我们想帮助虚拟机尽快的回收一对象(比如我在我们创建一些非常大的对象, 并且方法执行的时间很长; 或者是在一个很多的循环中, 创建了很多大的对象), 我们可以在用完后立马将引用置空, 这样可以help gc.
在我前面的一篇文章中,分析过netty的对象池技术,那里是自己主动进行对象回收和重复使用,在极端场景下, 想要更大的提高程序性能可以使用它.
链接: http://blog.csdn.net/levena/article/details/78144924