GC垃圾回收机制(Garbage Collection)
由机器自动负责回收不再使用的内存(主要回收的是堆中的对象)—垃圾回收是在释放内存,回收的基本单位是 “对象”,不是字节.
确定该对象是垃圾的规则 :
-
引用计数 :
使用额外的计数器,记录某个对象有多少引用指向它.(产生一个变量引用该对象,就把该对象的引用计数+1,销毁一个引用,就把该对象的引用计数-1(如果引用是局部变量,出了作用域就算是销毁了.如果引用是成员变量,引用对应的对象被销毁,则引用本身被销毁)
如果该对象的引用计数为0了则说明没有引用指向该对象此时该对象就可以被当做垃圾进行回收了)该方案的缺陷 :
-
多线程中,需要修改同一个引用计数,考虑线程安全问题
-
如果是大的对象,多引入一个计数器,空间开销不大;如果是小的对象,并且数量还多,引用计数就会造成不小的空间开销
-
可能会带来"循环引用"的问题.(致命缺陷)
class Test { Test ref = null; } Test a = new Test(); Test b = new Test(); a.ref = b; b.ref = a; //如果a,b出了他们的作用域,a,b被销毁,则a,b所对应的对象永远不会被销毁,空间也得不到释放(类似OS中的死锁)
-
-
可达性分析 : (Java中采用的方案)
以代码中一些被称为 GCRoot 的特殊变量作为起点,以起点出发,看看哪些对象能否被访问到,能访问到的对象就标记为 “可达”, 反之标记为 “不可达”(也就是需要回收的垃圾).—详细过程参考的一篇博客 : 大白话理解可达性分析算法_程序员小潘的博客-CSDN博客_可达性分析算法
垃圾回收算法 :
-
标记-清除 :
找到垃圾后,直接把垃圾释放掉.
缺陷 : 造成"内存碎片"问题,释放垃圾之后得到的内存空间不是连续的.
-
复制算法 :
把内存分为2块空间,开始时所有的对象全在一块空间中,找到垃圾之后,把不是垃圾的对象拷贝到另外一块空间,然后把这块空间全部释放掉.
缺陷 : 不存在内存碎片问题,但是空间利用率大大降低.
-
标记-整理 :
找到垃圾之后,把不是垃圾的对象进行前移(类似顺序表删除元素)
缺陷 : 前两种方案的缺陷得到解决,但前移操作提高了时间成本.
-
分代回收 : (JVM中主要采取的回收算法)
综合上述方案进行设计,不同场景使用不同方案.
该方案给对象引入 “年龄”(对象活过GC扫描的轮次…对象刚创建出来默认年龄为0,此后没被GC扫描一次依然存活,则年龄+1),根据年龄的不同(取决于GC的具体实现)将对象分为 “新生代” 和 “老年代”,把两类对象放到不同的内存区域中使用不同的回收算法
- 新的对象创建时放到伊甸区中
- 伊甸区中的对象绝大部分都活不过一轮GC (经验规律)
活过一轮GC的对象,就放到其中一个幸存区中(这个过程采用复制算法,由于存活的对象很少,所以复制开销不大,幸存区的空间也不必很大) - 幸存区中的对象,又会经过下一轮 GC的扫描,存活后的对象通过复制算法,拷贝到另外一个幸存区中
- 当对象在幸存区中,经历了多轮GC仍然没有被销毁,就认为这个对象应该一时半会不会被销毁,于是就通过复制算法把这个对象拷贝到老年代.
- 对象在老年代也仍然经过GC的扫描。但是扫描的频率会大大降低
老年代的对象如果被判定为垃圾,就使用标记整理算法(由于老年代对象被回收的频率不高,就可以接收标记整理
带来的时间开销)