什么是垃圾回收机制?
jvm内存结构猿们都知道 java 是垃圾自动回收的,项目启动就会有一个线程不定时的去内存堆里面清除满足回收条件的对象,释放内存空间。不需要猿们手动去回收垃圾,唯一能做的就是通知一下垃圾收集器去回收下垃圾,至于有没有真正去回收都是不可知的。当然垃圾收集器有自己的回收算法。
public class gc_demo {
public static class test {
@Override
protected void finalize() throws Throwable {
// gc回收垃圾之前调用
System.out.println("垃圾回收机制 finalize");
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
test b = new test();
}
System.gc(); // 通知垃圾收集器去回收下垃圾
}
}
JVM参数-查看GC日志 : -XX:+PrintGCDetails
finalize方法作用
- finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
- 它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。
哪些内存需要回收?
所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象,那么如何找到这些对象?
1、引用计数法
这个算法的实现是给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。这种算法使用场景很多,但是,Java中却没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况。看一段代码:
public class gc_demo {
public static class test {
private Object instance = null;
@Override
protected void finalize() throws Throwable {
// gc回收垃圾之前调用
System.out.println("垃圾回收机制 finalize");
}
}
public static void main(String[] args) {
test objectA = new test();
test objectB = new test();
objectA.instance = objectB;
objectB.instance = objectA;
objectA = null;
objectB = null;
System.gc(); // 手动回收垃圾
}
}
JVM参数:-XX:+PrintGCDetails
[GC (System.gc()) [PSYoungGen: 7014K->713K(56320K)]
[Full GC (System.gc()) [PSYoungGen: 713K->0K(56320K)]
看到,两个对象相互引用着,但是虚拟机还是把这两个对象回收掉了,这也说明虚拟机并不是通过引用计数法来判定对象是否存活的。
2、可达性分析法
这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
可以作为GCRoots的对象包括下面几种
- 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
如下图示例
GCRoots
由图可知,obj8、obj9、obj10都没有到GCRoots对象的引用链,即便obj9和obj10之间有引用链,他们还是会被当成垃圾处理,可以进行回收。
四种引用状态
四种引用状态方法区的垃圾回收
方法区的垃圾回收主要回收两部分内容:1. 废弃常量。2. 无用的类
废弃常量以字面量回收为例,如果一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了叫做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
无用的类需要满足以下三个条件:
- 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集算法
标记-清除(Mark-Sweep)算法
该算法有两个阶段。
- 标记阶段:首先标记出所有需要回收的对象
- 清除阶段:标记完成后统一回收所有被标记的对象
标记和清除两个过程的效率都不高 , 该算法一般应用于老年代,因为老年代的对象生命周期比较长。
复制(Copying)算法
复制算法是为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。这样每次只需要对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可。复制算法的执行过程如图:
coping算法一般是使用在新生代中,因为新生代中的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用coping算法进行拷贝时效率比较高。
标记-整理(Mark-Compact)算法
复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。万一对象100%存活,那么需要有额外的空间进行分配担保。老年代都是不易被回收的对象,对象存活率高,因此一般不能直接选用复制算法。根据老年代的特点,提出了另外一种标记-整理算法,过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。标记-整理算法的工作过程如图:
分代收集算法
分代即java堆-年轻代和老年代。无非是上面内容的结合罢了,根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。
大批对象死去、少量对象存活的(新生代),使用复制算法,复制成本低
对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法
垃圾收集器
垃圾收集器就是上面讲的理论知识的具体实现,我们使用的是HotSpot,HotSpot这个虚拟机所包含的所有收集器如图:
垃圾收集器
上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,那说明它们可以搭配使用。虚拟机所处的区域说明它是属于新生代收集器还是老年代收集器
网友评论