1 概述
前面已经介绍过了,jvm运行时的数据区域包括程序计数器、虚拟机栈、本地方法栈、堆和方法区,其中程序计数器、虚拟机栈和本地方法栈是线程私有的,当方法结束或者线程销毁时,这部分的内存就会被回收了,不用过多的考虑。所以,本章所讲的主要是堆和方法区的内存分配与回收问题。
2 对象“死”了吗
我们知道,堆里面几乎存放了java中所有的对象实例,垃圾收集器(GC)在对这部分内存进行回收时,首先需要判断对象是否还存活着,然后将“死”的对象或者是没有必要存活下去的对象进行回收。
2.1 引用计数算法
引用计数算法就是给每个对象添加一个计数器,当有地方引用它的时候,计数器就加1,当引用消失时,计数器就减1,计数器为0的对象可以认为该对象没有被引用,可以被垃圾收集器回收。不过,目前主流的java虚拟机并没有采用这种算法,主要原因是难以解决对象间相互引用的问题,即:Object A = B, Object B = A.
2.2 可达性分析法
可达性分析法就是通过从一系列称之为“GC Root”的点开始出发,向下面所有的对象开始搜索,如果“GC Root”无法到达一个对象,则这个对象就会被认为是可以被回收的对象,就会被垃圾收集器回收。
可以作为GC Root对象的主要有以下几种:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
2.3 To be or not to be
当一个对象被认为是可回收的时候,也不一定就会被回收,它们首先会被进行一次回收标记,然后判断这个对象是否有必要执行finalize()方法,如果没有必要执行或者是finalize()方法已经被jvm执行过了,那么这个对象就幸运的活了下来。当然,就算是认为有必要执行finalize()方法或者是jvm还没有执行finalize()方法,这个对象还是有机会活下来的。当一个对象在执行finalize()方法的时,只要重将自己与GC Root连接到,即GC Root与该对象是可到达的,那么这个对象就会被标记会“即将回收”,如果这个时候还没有拯救出自己的话,那么就会被垃圾收集器回收掉。另外需要注意一点的是,一个对象的finalize()方法只会执行一次,也就是说一个对象只要一次自救的机会。
3 垃圾回收算法思想
3.1 标记-清除算法
这种算法的思想就是将要回收的对象标记起来,然后统一的将这些对象清除掉,这是最基本的一种回收思想,不过这种算法有两个不足点:一个是效率问题,因为标记和清除两个操作效率都不高,二是空间问题,因为这种回收算法会导致大量的内存碎片,当需要一个很大内存空间来存储对象时,有时会难处理。
.3.2 复制算法
这种算法的思想就是将内存分成两部分,分配内存时只使用一部分,当垃圾回收时,将未回收的复制到另一部分内存中,这样虽然不会产出内存碎片,但是会浪费一半的内存使用空间。
3.3 标记-整理算法
这种算法思想首先是标记要回收的对象,然后将要清理的对象向一边移动,不需要回收的对象向另一边移动,最后直接将需要清理的对象回收掉即可。
3.4 分代收集算法
这种算法是根据对象不同的存活周期划分为几块,一般分为新生代和老年代,根据不同年代的特点采取不同的回收算法。