- 对象存活判定算法
- 垃圾回收区域
- 垃圾回收算法
- 垃圾回收器
一、对象存活判定算法
1、引用计数算法
给每个对象添加一个引用计数器,当有地方引用它时,计数器值就加1,当引用失效时,计数器值就减1,当计数器值为0时,对象就不再被引用。但主流的java虚拟机没有使用这种算法,主要原因是它难以解决对象之间循环引用的问题。
2、可达性分析算法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到“GC Roots”没有任何引用链相连时,则证明此对象不可用,会判断为可回收对象。
可作为GC Roots的对象:
(1) 虚拟机栈中本地变量表引用的对象
(2)本地方法栈中Native方法引用的对象
(3)方法区中类static静态属性引用的对象
(4)方法区中final常量引用的对象
注意:可达性分析算法中判定不可达的对象还未真的判死刑,还至少要经历两次标记过程:判断对象是否有必要执行finalize()方法,如果有必要则执行finalize()方法,之后还会对对象进行一次筛选,如果对象能在finalize()中重新与引用链上的任何一个对象建立关联,将被移除出即将回收的集合。
二、垃圾回收区域
1、堆内存
这是垃圾回收的主要区域,新生代常规应用进行一次GC一般可回收70%~95%的空间,永久代的GC效率远低于此。
2、方法区
主要回收两部分内容:废弃常量和无用的类。
无用的类需满足三个条件:
(1)该类在堆中的所有实例都被回收
(2)加载该类的Class Loader已被回收
(3)该类对应的java.lang.Class对象没在任何地方被引用,即无法在任何地方通过反射访问该类的方法。
三、垃圾回收算法
1、标记-清除算法
首先【标记】出所有需要回收的对象,然后统一【清除】所有被标记的对象。其存在着两个不足:
(1)效率问题:标记和清除两个过程的效率都不高。
(2)空间问题:空间碎片太多,标记清除之后会产生大量不连续的内存碎片,可能会导致后续需要分配较大对象时,因无法找到足够的连续内存而提前触发另一次GC,影响系统性能。
2、复制算法
复制算法是将可用内存划分为大小相等的两块,每次只使用其中的一块,当一块的内存用尽后,把还存活着的对象复制到另外一块上面,然后将已使用的内存空间一次清理掉。
优点:不用考虑内存碎片问题。
缺点:每次可使用的内存缩小为原来的一半,内存使用率低,当对象存活率较高时,就要较多的复制操作,效率也会降低。
需要注意的是,通过研究表明,新生代中的对象98%是朝生夕死的,因此没有必要按1:1来划分内存空间,而是分为一块较大的Eden空间和两块较小的Survivor空间,在HotSpot虚拟机中默认比例为8:1:1。每次使用Eden和一块Survivor,回收时将这两块中存活的对象一次性复制到另一块Survivor上,然后清理掉Eden和Survivor,可见这样只有10%的内存被“浪费”,倘若Survivor空间不足,需要老年代内存担保,这些对象将直接进入老年代中。
3、标记-整理算法
首先【标记】出所有需要回收的对象,然后进行【整理】,使得存活的对象都向一边移动,最后直接清理掉端边界以外的内存。
4、分代算法
根据对象存活周期的不同一般将内存划分为“年轻代”和“老年代”。新生代中,每次GC只有少量的对象存活,可以选用复制算法;老年代中,对象的存活率高,没有额外的担保空间,可以使用标记-清理和标记-整理算法。
四、垃圾回收器
1、垃圾回收算法性能
(1)吞吐量:代码运行时间 / (代码运行时间 + 垃圾收集时间),吞吐量越高,CPU的利用率越高,则算法越好。
(2)最大暂停时间:因GC而暂停应用程序线程的最长时间。暂停时间越短,则算法越好。
注意:高吞吐量和低暂停时间不可兼得,一般为了获得最大的吞吐量,就会尽量少的进行GC,但推迟运行GC会导致积累更多的对象等待回收,这样每次GC的时间会变高,由此引起的平均和最大暂停时间也会变高。
2、HotSpot垃圾回收器
如图所示两个收集器之间有连线,表示可搭配使用,其具体的功能比较如下图: