jvm要做垃圾回收时,首先要判断一个对象是否还有可能被使用。
那么如何判断一个对象是否还有可能被用到?
如果我们的程序无法再引用到该对象,那么这个对象就肯定可以被回收,这个状态称为不可达。当对象不可达,该对象就可以作为回收对象被垃圾回收器回收。
那么这个可达还是不可达如何判断呢?
在此之前,我们先来复习一个和Java对象回收有关的知识,那便是finalize方法,这是一个在Object类中定义的方法,如果我们重写了finalize方法,那么在对象被回收之前将会调用finalize方法,如果我们在finalize方法中将对象和某个还在生命周期的对象关联上,那么这个对象还有可能在回收之前被复活,当然这种机会只有一次,当第二次遇到回收时,将不会再调用finalize方法。
下面我们正式介绍Java对象是否存活的判断算法——根搜索算法。
这个算法的思路其实很简单,它把内存中的每一个对象都看作一个节点,并且定义了一些对象作为根节点“GC Roots”。如果一个对象中有另一个对象的引用,那么就认为第一个对象有一条指向第二个对象的边,如下图所示。JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。
答案就是GC roots ,也就是根对象,如果从一个对象没有到达根对象的路径,或者说从根对象开始无法引用到该对象,该对象就是不可达的。
这个算法的关键就在于GC Roots的定义,教科书中给出了四种作为GC Roots的对象。以下三类对象在jvm中作为GC roots,来判断一个对象是否可以被回收
(通常来说我们只要知道虚拟机栈和静态引用就够了)
-
虚拟机栈(JVM stack)中引用的对象(准确的说是虚拟机栈中的栈帧(frames))
我们知道,每个方法执行的时候,jvm都会创建一个相应的栈帧(栈帧中包括操作数栈、局部变量表、运行时常量池的引用),栈帧中包含这在方法内部使用的所有对象的引用(当然还有其他的基本类型数据),当方法执行完后,该栈帧会从虚拟机栈中出栈,这样一来,临时创建的对象的引用也就不存在了,或者说没有任何gc roots指向这些临时对象,这些对象在下一次GC时便会被回收掉 -
方法区中类静态属性引用的对象
也就是使用了static关键字。静态属性是该类型(class)的属性,不单独属于任何实例,因此该属性自然会作为gc roots。只要这个class存在,该引用指向的对象也会一直存在。 -
本地方法栈(Native Stack)引用的对象
-
常量引用,就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots
一个class要被回收准确的说应该是卸载,必须同时满足以下三个条件
- 堆中不存在该类的任何实例
- 加载该类的classloader已经被回收
- 该类的java.lang.Class对象没有在任何地方被引用,也就是说无法通过 - 反射再带访问该类的信息
至此,我们已经了解了平时生成的大部分对象是如何被JVM标记为可回收的。但是在Java中,还存在一些其它的情况,这就要从引用讲起了。我们平时使用的Java对象通常认为只有两种状态,一种是被引用了,在程序中还在使用,另一种是没有被引用,可以被JVM回收。但实际上,Java中的引用一共有四种,它们分别是强引用、软引用、弱引用和虚引用,下面我们来分别介绍。
其实这四类引用的区别就在于GC时是否回收该对象
- 强引用(Strong) :就是我们平时使用的方式 A a = new A();强引用的对象是不会被回收的。
- 软引用(Soft) :在jvm要内存溢出(OOM)时,会回收软引用的对象,释放更多内存。
- 弱引用(Weak) :在下次GC时,弱引用的对象是一定会被回收的
- 虚引用(Phantom) :对对象的存在时间没有任何影响,也无法引用对象实力,唯一的作用就是在该对象被回收时收到一个系统通知
网友评论