对象已死的判断标志
引用计数法
引用计数法是一种简单但效率高的判断方法,算法的实现思路是:在对象中添加一个引用计数器,每当一个地方引用它时就加一;当引用失效时计数器就减一。因此,任何时刻计数器为零的对象就是不可能被使用的对象,即被标记为该对象已死亡。
但是,这个算法有一个很致命的缺点:很难解决对象之间相互循环引用的问题,它们之间互相引用着对方,导致它们的引用计数都不会变成零。所以现在主流的虚拟机没有选用这个算法来管理内存。
可达性分析算法
可达性分析算法的实现思路:通过一系列的称为“GC Root”的对象集合作为起始点,从集合中的元素开始向下搜索,搜索经过的路径称引用链,当一个对象到GC Roots没有任何引用链相连(图论:从GC Roots到这个对象不可达)时,则证明此对象是不可用的,就会标记为对象已死。
在上图中,即使Object5、6、7之间存在着关联,但是它们和GC Root Set并没有任何关联,因此这三个对象都会被标记为对象已死。
能作为GC Root Set元素的对象一般包括以下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,比如:当前正在运行的方法所使用到的参数、局部变量、临时变量
- 在方法区中类静态属性引用的对象,比如:Java类的引用类型静态变量
- 在方法区中常量引用的对象,比如:字符串常量池里的引用
- 在本地方法栈中Native方法引用的对象
-
引用
Java引用的定义:如果reference类型的数据中存储的数值代表另一块内存的起始地址,就称该reference数据代表的是某块内存或对象的引用。在JDK1.2版本后,Java对引用的概念进行的拓展,将引用分为四种类型:强引用、软引用、弱引用、虚引用,这4种引用强度依次逐渐减弱。
强引用
强引用是最传统的引用定义,是指在程序代码之中普遍存在的引用赋值,即类似“Obkect obj=new Object()”这种引用关系。无论在什么时候,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用
软引用是描述一些“还有用、但非必须”的对象。只被软引用关联的对象,在系统将要发送内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收后还是没有足够的内存,才会抛出内存溢出异常。
弱引用
弱引用也是用来描述那些非必须的对象,但是它的引用关系比软引用更加弱一些。被弱引用关联的对象,无论当前内存是否足够,都只能生存到下一次垃圾回收之前。
虚引用
虚引用是Java中最弱的引用,一个对象是否有虚引用的存在,完全不会对其生存时间造成任何影响,也无法通过虚引用来取得一个对象实例。虚引用存在的唯一目的就是为了在被关联的象被回收时能收到一个系统通知。
对象的自救
现在主流的虚拟机采用的可达性分析算法中被标记为不可达的对象其实也并不是“非死不可”,这些对象还可以抓住“仅此一次的自救方法”。不可达对象被宣布真正“判死刑”需要经过两个阶段:
如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有重写finalize()方法或finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行执行finalize()方法”
- 如果对象获得了被执行finalize()方法的权限,那么这个对象将会被放入F-Queue的队列之中,并在稍后由Finalizer线程来执行它,只要在执行过程中重新与引用链上的一个对象建立关联,那么这个对象就成功拯救了自己。
注意: 任何一个对象的finalize()方法都只会被系统自动调用一次,这个方法也是对象自救的唯一的一次方法。而且,Java官方其实建议不要使用这个方法(即不要重写)。
回收方法区
在Java堆中,尤其是在新生代,进行一次常规的垃圾回收可以回收70%~90%的内存空间。但是回收方法区中的资源的条件就比较苛刻,其回收效率一般低于堆。方法区的垃圾回收的主要对象:废弃的常量和不再使用的类。
- 废弃常量:常量池中的常量没有其他地方引用它。
- 不再使用的类:
- 该类的实例都已经被回收,即Java堆中不存在该类及其任何派送子类的任何实例
- 加载该类的ClassLoader已经被回收,这个条件除了精心设计外是比较难以达成的
- 该类的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
在大量使用反射,代理、频繁自定义类加载器等场景下都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。