- 概述
垃圾收集(Garbage Collection, 简称GC)需要完成的三件事情:
哪些内存需要回收? 什么时候回收? 如何回收?
Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地⽅法栈3个区域随线程而生,随线程而灭,因此这几个区域的内存分配和回收都具备确定性, 在这几个区域内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。
垃圾收集器所关注的内存回收指的是JAVA堆和方法区。
- 判断对象死亡的两种方法
引用计数器
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
优点:实现简单,效率高
缺点:不能解决对象之间循环引用的问题
可达性分析算法(根搜索算法)
设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则这个对象可以被回收。
根可达分析:通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,说明从GC Roots到这个对象不可达时,则此对象是不可能再被使用的。
在Java技术体系⾥⾯,固定可作为GC Roots的对象包括以下⼏种:
·在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调⽤的⽅法堆栈中使用到的参数、局部变量、临时变量等。
·在⽅法区中类静态属性引⽤的对象,譬如Java类的引⽤类型静态变量。
·在⽅法区中常量引⽤的对象,譬如字符串常量池(String Table)⾥的引⽤。·在本地⽅法栈中JNI(即通常所说的Native⽅法)引⽤的对象。
·Java虚拟机内部的引⽤,如基本数据类型对应的Class对象,⼀些常驻的异常对象
(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
·所有被同步锁(synchronized关键字)持有的对象。
·反映Java虚拟机内部情况的JM XBean、JVM TI中注册的回调、本地代码缓存等
在根搜索算法的基础上,现在JVM的实现当中,垃圾收集的算法主要有三种:标记-清除算法,标记-复制算法,标记-整理算法,这三种算法都扩充了根搜索算法。
三、引用
我们希望能描述一类对象:当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象——很多系统的缓存功能都符合这样的应用场景。
在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Reference)、软 引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。
1.强引用(Strongly Reference)
像“Object obj=new Object()”这种引用关系,类似于必不可少的生活用品。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。当内存空间不足,JVM宁愿抛OOM,使程序异常终止,也不会回收具有强引用的对象来解决内存不足的问题。
2.软引用(Soft Reference)
如果一个对象只具有软引用,那就类似于还有用但非必须的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
3.弱引用(Weak Reference)
如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
4.虚引用(Phantom Reference)
最弱的一种引用关系。虚引用并不会决定对象的生命同期,也无法通过虚引用来取得一个实例对象。为⼀个对象设置虚引⽤关联的唯⼀⽬的只是为了能在这个对象被收集器回收时收到⼀个系统通知。用于管理堆外内存。
即使在可达性分析算法中判定为不可达的对象,也不是“⾮死不可”的,这时候它们暂时还处于“缓刑”阶段,要真正宣告⼀个对象死亡,⾄少要经历两次标记过程: 如果对象在进⾏可达性分析后发现没有与GC Roots相连接的引⽤链,那它将会被第⼀次标记,随后进⾏⼀次筛选,筛选的条件是此对象是否有必要执⾏finalize()⽅法。假如对象没有覆盖finalize()⽅法,或者finalize()⽅法已经被虚拟机调⽤过,那么虚拟机将这两种情况都视为没有必要执行。如果这个对象被判定为确有必要执⾏finalize()⽅法,那么该对象将会被放置在⼀个名为F-Queue的队列之中,并在稍后由⼀条由虚拟机⾃动建⽴的、低调度优先
级的Finalizer线程去执⾏它们的finalize()⽅法。finalize()⽅法是对象逃脱死亡命运的最后⼀
次机会,稍后收集器将对F-Queue中的对象进⾏第二次⼩规模的标记,如果对象要在finalize()中成功拯救⾃⼰——只要重新与引⽤链上的任何⼀个对象建⽴关联即可,譬如把⾃⼰(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出即将回收的集合; 如果对象这时候还没有逃脱,那基本上它就真的要被回收了。(这种⾃救的机会只有⼀次,因为⼀个对象的finalize()方法最多只会被系统⾃动调⽤⼀次)
四、回收⽅法区
⽅法区的垃圾收集主要回收两部分内容: 废弃的常量和不再使⽤的类型。
回收废弃常量与回收Java堆中的对象⾮常类似。判定⼀个常量是否“废弃”还是相对简单,⽽要判定⼀个类型是否属于“不再被使⽤的类”的条件就⽐较苛刻了。需要同时满⾜下⾯三个条件:
·该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派⽣⼦类的实例。
·加载该类的类加载器已经被回收,这个条件除⾮是经过精⼼设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
·该类对应的java.lang.Class对象没有在任何地⽅被引⽤,⽆法在任何地⽅通过反射访问该类的⽅法。
垃圾回收算法见下篇