java垃圾回收的区域和判断条件
如何判断对象是否需要回收
1、引用计数法
给对象添加一个引用计数器,某个地方引用,加一,失效,减一。
原理简单,效率高,但是难以解决对象之间相互循环使用的问题。所以java虚拟机并没有使用它
2、可达性分析
有一些对象可称为GC Roots作为根对象,根据引用关系向下搜索,与引用链上无关的对象说明它不可达,即此对象不能再使用。
可作为GC Roots的对象
- 虚拟机栈中引用的对象
- java类的引用类型静态变量
- 方法区中常量引用的对象
- Native方法引用的对象
- 被同步锁持有的对象
- Class对象
以上两种方法在判断对象存活时与引用离不开关系,所以引用如何分类
引用的粗略定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址, 就称该reference数据是代表某块内存、 某个对象的引用。
这样的定义无法满足复杂回收情况时的判断条件,所以java对引用进行了扩充:
强引用:
类似“Object obj=new Object()”这种引用关系。 无论任何情况下, 只要强引用关系还存在, 垃圾收集器就永远不会回收掉被引用的对象。
软引用:
有用,非必须的的对象。只被软引用关联着的对象, 在系统将要发生内存溢出异常前, 会把这些对象列进回收范围之中进行第二次回收。
弱引用:
描述那些非必须对象, 但是它的强度比软引用更弱一些, 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。 当垃圾收集器开始工作, 无论当前内存是否足够, 都会回收掉只被弱引用关联的对象。
虚引用:
一个对象是否有虚引用的存在, 完全不会对其生存时间构成影响, 也无法通过虚引用来取得一个对象实例。 为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。
经过了可达性分析和引用分类,虚拟机暂时清楚了该回收哪些对象,但对于可达性分析过程中,还会给对象一次机会
第一次标记:对象第一次发现无引用链时,会被第一次标记,随后判断该对象是否有必要执行finalize方法。如有必要执行,对象就被送到F-Queue队列中。它获得了救活自己的机会
第二次标记:随后虚拟机会启动一个线程执行F-Queue队列中对象的finalize方法,如果某个对象在这个过程中成功又进入了引用链中,它就避免了第二次被标记
方法区的垃圾回收条件
堆上的垃圾回收判断如上所述,对于方法区上的,因为其苛刻的判断条件,使得回收成本高。它主要回收废弃的常量和不再使用的类型 。判断常量回收比较容易,而对于类型不再被使用有如下条件:
- 该类所有的实例都已经被回收, 也就是Java堆中不存在该类及其任何派生子类的实例。
- 加载该类的类加载器已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用, 无法在任何地方通过反射访问该类的方法。
满足上述条件后,使得方法区的某个类型允许被回收,但是否进行回收,还需要其他参数控制。
垃圾收集算法
根据对象的存活时间,可将java堆划分出两个区域:新生代和老年代。新生代存放那些朝生夕灭的对象,老年代存放那些难以消亡的对象。进而在回收频率上,对新生代经常回收,老年代则间隔长些。但划分区域并不是如此简单,会有跨代引用的问题,如果在对新生代进行回收时,某个对象是被老年代中的对象引用,碍于这种情况,每次都要对老年代中的对象进行遍历。但是经过验证,有了另一条经验:存在互相引用关系的两个对象, 是应该倾向于同时生存或者同时消亡的,并且在对新生代的回收时,那些存在跨代引用的对象也应该升级到老年代中。所以我们不应该为了少量的跨代引用去扫描整个老年代,只需在新生代上建立一个全局的数据结构,这个结构把老年代划分成若干小块, 标识出老年代的哪一块内存会存在跨代引用。 此后当发生Minor GC时, 只有包含了跨代引用的小块内存里的对象才会被加入到GCRoots进行扫描。
区域划分开后,就针对不同的区域有了不同的回收类型和算法。
标记清除算法
首先标记出所有需要回收的对象, 在标记完成后, 统一回收掉所有被标记的对象, 也可以反过来, 标记存活的对象, 统一回收所有未被标记的对象。
缺点:1、执行效率不稳定 2、内存碎片
标记复制算法
为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,它将可用内存按容量划分为大小相等的两块, 每次只使用其中的一块。 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。
缺点是可用内存缩小到了原来的一般
优化后的半区复制算法:把新生代分为一块较大的Eden空间和两块较小的Survivor空间, 每次分配内存只使用Eden和其中一块Survivor。 发生垃圾搜集时, 将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上, 然后直接清理掉Eden和已用过的那块Survivor空间。
标记整理算法
其中的标记过程仍然与“标记-清除”算法一样, 但后续步骤不是直接对可回收对象进行清理, 而是让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存。
GC类型
触发时机 | GC目标 | 耗时 | |
Minor GC | Eden区满时触发,Survivor区满不会触发,但Minor GC会引起Survivor区垃圾回收 | Eden区和Survivor区 | 很快,STW时间短 |
Major GC | 老年代空间不足时,会先尝试触发Minor GC 如果空间还不足,则触发Major GC | 只有老年代,只有CMS GC会有单独收集老年代的行为 | 慢10倍以上 STW时间长 |
Full GC | 1、调用System.gc()时 2、老年代空间不足 3、方法区空间不足 4、通过Minor GC后进入老年代的平均大小大于老年代的可用内存 5、由Eden区、s0区向s1区复制时,对象大小大于s1可用内存,则把对象转存到老年代且老年代的可用内存小于该对象大小 | 整个java堆和方法区的垃圾收集 | 10倍以上,full GC是开发和调优中尽量避免的 |