GC回收区域
GC主要针对堆区回收,回收是以对象为单位。方法区的类对象加载后不太需要回收;栈区的释放时机确定,不必回收;程序计数器是固定内存地址,不必回收。
找出垃圾的方法
- 引用计数法(jvm未采取)
给每个对象都加上计数器,用来表示有当前对象有几个引用,每次多一个引用指向该对象,计数器就+1,每次少一个引用指向该对象,计数器就-1.当计数器的值为0时,就说明当前对象已经无人在引用,可以释放。
优点
简单,容易实现,执行效率高
缺点
空间利用率低,尤其是小对象,比如一个计数器是int大小,这个对象只有一个int大小的成员.
可能出现循环引用的情况,例子如下面代码
class Test {
Test test;
}
Test a = new Test();//a计数器+1
Test b = new Test();//b计数器+1
a.test = b;//b计数器+1
b.test = a;//a计数器+1
a = null;
b = null;
已经给a 和b 赋值为null,已经没有引用指向该对象,但是由于循环引用,这两个对象的计数器都为1并不是0,导致这两个对象都无法释放
- 可达性分析(jvm采取)
约定一些特定的变量为"GC roots",每隔一段时间,从"GC roots"出发,进行遍历,看当前哪些变量能够访问到,能够访问到的变量称为"可达",否则"不可达"
哪些变量可以成为GC roots?
栈上的变量
常量池引用的对象
方法区引用类型的静态变量
回收垃圾的方法
- 标记清除法
- 复制算法
- 标记整理法
相对于复制算法,空间利用率提高了,同时也解决了内存碎片问题,但是搬运操作比较耗时
- 分代回收法
上面三个方法都不完美,我们需要根据实际场景因地制宜的解决问题.而分代回收法就是根据对象年龄(GC轮次,一个对象经历一次GC年龄+1)采取不同回收方法.
刚创建出来的对象进入伊甸区,如果新对象熬过一轮GC就通过复制算法,复制到生存区.生存区的对象每熬过一轮GC,就通过复制算法拷贝到另一个生存区,只要这个对象不消亡就会在这两个生存区来回拷贝,每一轮GC都会筛选掉很多对象.如果一个对象在生存区反复坚持很多轮还没事,就会进入老年带,
也会定期GC,但是频率更低,在老年代采取标记整理法来处理老年代对象.
特殊情况
如果是大对象,直接进入老年代.因为大对象进行复制算法,开销过大,而且创建大对象,一般不会立即回收