如何确定垃圾
- 引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能在被使用的。
优点:实现简单,判定效率也很高。
缺点:很难解决对象之间相互引用的问题。所以,没有被主流的Java虚拟机所采用。 - 可达性分析算法
通过一系列的称为“GC Roots”的对象作为起始点,从这些根节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,即从GC Roots到这个对象不可达时,则证明此对象时不可用的。
可以作为GC Roots 的对象包括:- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(即一般说的Native 方法)引用的对象。
如何逃避回收
对象被宣告真正死亡,至少要经过两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会第一次被标记并且进行一次筛选,筛选的条件时此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,对象就被判定死亡。如果对象被判定有必要执行finalize()方法,那么这个对象就会放置在一个叫F-Queue的队列之中,并稍后由一个Finalizer线程去触发这个方法,但可能不会等待它运行结束。如果对象在第二次标记前,重新和引用链上的任何一个对象建立关联,那么在第二次标记时它将被移出“即将回收”的集合。对象重获新生。
如何回收垃圾
- 标记-清除算法(Mark-Sweep)
首先,标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
不足:
- 效率问题,标记和清除两个过程的效率都不高。
- 空间问题,会产生大量不连续的内存碎片,可能会导致大对象找不到足够的连续内存空间导致提前触发另一次垃圾收集动作。
- 复制算法(Copying)
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存清理掉。
优点:实现简单,运行高效,不产生碎片。
缺点:可用内存被压缩到了原本的一半,且存活对象增多时,Copying算法效率大大降低。
备注:新生代垃圾回收算法,因为新生代的对象存活较少 - 标记-整理算法(Mark-Compact)
标记阶段和标记-清理算法相同, 但标记后不是清理对象,而是将存活对象移向内存的一端,然后清理掉端边界外的内存。
备注:作为老年代垃圾收集算法,因为对象存活率高,没有额外空间对它进行分配担保。
- 分代收集算法
根据对象存活周期的不同,将Java堆分为新生代和老年代。在新生代中,每次垃圾收集时都由大批对象死去,只有少量存活,选用复制算法;在老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法进行回收。
新生代
目前大部分JVM的GC对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的Eden(8/10)空间和两个较小的Survivor空间(From Space(1/10), To Space(1/10)),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。
老年代
而老年代因为每次只回收少量对象,因而采用Mark-Compact算法。
永久代(在Jdk8被元空间所取代)
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
废弃常量:没有对象引用常量池中的常量。
无用的类:
- Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
总结
- 处于方法区的永久代(Permanet Generation),它用来存储class类,常量,方法描述等。对永久代的回收主要包括废弃常量和无用的类。
- 对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space,少数情况会直接分配到老生代。
- 当新生代的Eden Space和From Space空间不足时就会发生一次Minor GC,进行GC后,Eden Space和From Space区的存活对象会被挪到To Space,然后将Eden Space和From Space进行清理。
- 如果To Space无法足够存储某个对象,则将这个对象存储到老年代。
- 在进行GC后,交换From Space和To Space,如此反复循环。
- 当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄到达15的对象会被移到老年代中。