首先讲讲gc机制。很多人在面试中被问到jvm的gc机制,都会自然而然地回答为引用计数法。在我们一些常见的虚拟机中,比如python的虚拟机,的确是采用引用计数法来标记垃圾对象的,但在主流的jvm中,采用的是另一机制,可达性分析。可达性分析将所有对象及其引用关系看作一个图结构,从某个节点(GCroot节点)到某个节点是联通的,我们就说这个对象是活的,反之,就是需要被回收的对象。
根据深入理解jvm这本书所讲述的,有以下几种对象可以作为GCroot:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象;
2.方法区中类静态属性引用的对象;
3.方法区中常量引用的对象;
4.本地方法栈中JNI(即一般说的Native方法)引用的对象.
其实不止这四种,还有class loader和其他对象也可以作为根节点。下面看一段代码:
public class StrongReference {
public static void main(String[] args) {
//在执行完这个方法后,person引用空间被回收,不在指向Person对象,该对象则不能作为根节点,则应该回收
Person person = new Person(); //虚拟机栈中引用的对象为根节点
try {
while(true){
new ArrayList<Person>().add(person);
}
} catch (Throwable e) {
person.printPerson();
throw e;
}
}
}
执行上述代码,我们发现,这段代码会一直无限循环,永远不会抛出内存溢出错误。这显然与我们的经验很不符合。答案很简单:在循环体中执行new ArrayList<Person>().add(person);时,虽然不断在堆中创建ArrayList对象,但从根节点person并不存在到ArrayList的一条引用链,所以jvm会自动回收掉这个对象,所以即使一直循环,也不会内存溢出;
修改代码如下:
package reference;
import java.util.ArrayList;
import java.util.List;
public class StrongReference {
public static void main(String[] args) {
//在执行完这个方法后,person引用空间被回收,不在指向Person对象,该对象则不能作为根节点,则应该回收
Person person = new Person(); //虚拟机栈中引用的对象为根节点
List<Person> list = new ArrayList<Person>();
try {
while(true){
list.add(new Person());
}
} catch (Throwable e) {
person.printPerson();
throw e;
}
}
}
执行上述代码,我们发现输出this is person并抛出OutOfMemoryError错误。这是因为list对象是根节点,list对象中有一个引用指向elementData数组,elementData数组中又存放着不断生成 的person对象,因此从根节点list对象到person对象存在一条可达的引用链,因此在方法结束之前,jvm永远不会回收person对象,则发生内存溢出错误。
在一个方法执行完后,该方法所对应的栈帧在虚拟机栈中出栈,栈帧中保存的局部变量表也会释放,原本局部变量引用的对象失去了局部变量中的引用,就成为普通节点,而不是根节点,成为普通节点后,如果没有与其他根节点有可达的引用链,则该对象也会被回收。
说完jvm的gc机制,我们在看看java中的引用类型。自从jdk1.2之后,java对引用的概念进行了扩充,分别分为强引用(StrongReference),软引用(SoftReference),弱引用(WeakRefrnence)和虚引用(PhantomReference)四种。下面我们分别谈谈:
1.强引用。对形如Object obj = new Object()产生的引用都是都是强引用,只要强引用还存在,jvm就永远不会回收该对象
2.软引用。软引用主要用来指向一些有用但不重要的对象。如果内存足够,jvm不会回收软引用指向的对象,如果内存紧张,在发生溢出错误之前,jvm会回收该对象。
这点可以用来实现一些持久化对象的缓存。比如从数据库中查询得到的对象,可能过一段时间后,还会再次使用该对象,将它使用软引用关联,就可以避免频繁的查询数据库,
也不用担心会撑爆内存。代码如下:
package reference;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
public class Soft_Reference {
public static void main(String[] args) {
SoftReference<Person> sf = new SoftReference<Person>(new Person());
sf.get().printPerson(); //此时可以输出person对象信息
List<Person> list = new ArrayList<Person>();
try {
while(true){
list.add(new Person());
}
} catch (Throwable e) {
sf.get().printPerson();//因为循环导致内存溢出,所以该软引用指向的对象已经被回收,sf.get()返回的是null;
throw e;
}
}
}
3.弱引用:弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
package reference;
import java.lang.ref.WeakReference;
public class Weak_Reference {
public static void main(String[] args) {
WeakReference<Person> wr = new WeakReference<Person>(new Person());
wr.get().printPerson();//输出信息
System.gc(); //gc机制开始工作
wr.get().printPerson(); //因为已经被回收,所以会出现空指针异常
}
}
4.虚引用也称为幽灵引用或者幻影引用。它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用