一、什么时候垃圾回收
- GC是有JVM自动完成的,是根据JVM系统环境决定的,时机是不确定的
- 当然可以手动的调用System.gc()方法进行垃圾回收的,但是这样不推荐,消耗资源。即时这样调用,也只能是通知JVM进行垃圾回收,具体啥时候还要看JVM。
- Eden区或者S去不够用了,不是满了,是不够用了
- 老年代不够用了
- 方法区不够用了
- System.gc()
- 已经确定一个对象是垃圾了。那么要怎样才能回收它呢?就要用到垃圾回收算法
二、标记-清除
- 先用GC ROOT可达性算法标记出有用对象。
- 然后将没用的直接清除
- 问题:
- 2次遍历效率低
- 会造成内存碎片。当下一次分配新的大对象的时候,会因为内存不足导致触发GC
- 流程图如下
- 标记-清除算法,衍生出来了对象的分配算法
- 首次适应算法:就是遍历我们空闲链表,只要找出跟新对象大小一样的空间,然后分配给对象,并且立即返回
- 最佳适应算法:遍历整个空闲链表,找出跟新对象大小一样的空间,分配给对象,一般都是找到的第一个空间
- 最差适应算法:遍历整个空闲链表,找出最大的那一块,然后分割给对象。但是这有问题就是造成空间碎片
三、复制
- 将内存划分成两块
- 先用GC ROOT标记这一块的存活对象,然后复制到另一块里面去。
- 将那一块区域整体清掉
- 问题
- GC的垃圾收集,其实是一种穷人算法,如果你有钱,内存很大,还GC个菠萝。这种复制算法会浪费内存
- 所以一般用于小一点的内存,比如说我们的S1和S2区
- 流程如下
四、标记-整理
其实标记整理应该叫做标记-清除-整理,因为它是先标记,在清除,最后整理的
- 先用GC ROOT可达性算法标记出有用对象。
- 然后将没用的直接清除
- 然后整理内存
- 流程如下
- 双指针整理算法、Lisp2整理算法(滑动整理算法)、单次遍历算法
双指针整理算法
- first指针从头开始,second指针从尾开始。
- 第一阶段(遍历一次):
- 当first指针到达空闲内存的时候停住,然后second到达可达性对象的时候停住
- 将second的可达性对象移动到first的空闲内存哪里
- 直到两个指针相碰撞
- 第二阶段(遍历一次):
- 将GC ROOT的引用改变为当前移动后的地址。因为第一阶段只是移动了内存中的对象,并没有将GC ROOT上的引用改变
- 问题:
- 只能处理对象大小相等的情况
- 对象在内存中没有“连续性”了,将对象打散到内存里面了。这样会导致,读取的时候要读很多个cache line。
- 流程图如下
Lisp2整理算法
- free指针,scan指针从头开始,end指针永远挂在最后一个位置
- 第一阶段(遍历一次):
- scan指针往前走,当遇到一个可达性对象的时候,看这个可达性对象大小是多少,free就往前走多少
- 直到scan和end相碰撞,end其实就是用来跟scan做碰撞的
- 第二阶段(遍历一次):
- 将GC ROOT的引用改变成将要移动的地方
- 第三阶段(遍历一次):
- 移动对象到指定的位置
- 问题:
- 遍历3次有点慢啊
- 流程:
单次遍历算法
- 其实就是4.2的Lisp2算法改良
- 在内存中单独开辟一片空间,去存放对象当前的位置、对象要移动的位置等信息。
- 第一阶段就是先收集信息,第二阶段拿这个信息直接进行移动。