什么时候回收对象
引用计数法
1, 原理: 为对象添加一个引用计数器, 当对象增加一个引用时计数器加 1, 引用失效时计数器减 1. 引用计数为 0 的对象可被回收.
2, 缺点: 无法解决循环引用问题
可达性分析
1, 原理: 以 GC Roots 为起始点进行搜索, 可达的对象都是存活的, 不可达的对象可被回收.
2, 可作为 GC Root 的对象:
虚拟机栈中局部变量表中引用的对象
本地方法栈中 JNI 中引用的对象
方法区中类静态属性引用的对象
方法区中的常量引用的对象
方法区回收
1, 主要是对类的卸载和对常量池的回收, 对于大量引用动态代理和反射的场景下, 类的卸载是具有重要意义的
2, 类的卸载需满足以下三个条件, 但是就算三个条件都满足也不一定就被卸载
该类所有的实例都已经被回收, 此时堆中不存在该类的任何实例.
加载该类的 ClassLoader 已经被回收.
该类对应的 Class 对象没有在任何地方被引用, 也就无法在任何地方通过反射访问该类方法.
finalize 方法
1, 类似 C++ 的析构函数, 用于关闭外部资源. 但是 try-finally 等方式可以做得更好, 并且该方法运行代价很高, 不确定性大, 无法保证各个对象的调用顺序, 因此最好不要使用.
2, 当一个对象可被回收时, 如果需要执行该对象的 finalize() 方法, 那么就有可能在该方法中让对象重新被引用, 从而实现自救.
自救只能进行一次, 如果回收的对象之前调用了 finalize() 方法自救, 后面回收时不会再调用该方法
四种引用
不管是引用计数法还是可达性分析, 引用的判断与计数都是很重要的
强引用
1, 特点: 不会被回收
2, 构造方式: new
1 Object object = new Object();
软应用
1, 特点: 只有在内存不够的时候才会被回收
2, 构造方式: SoftReference
- Object object = new Object();
- SoftReference<Object> sf = new SoftReference<Object>(object);
- object = null;// 使对象只能被软引用关联
弱引用
1, 特点: 下一次内存回收的时候一定会被回收
2, 构造方式: WeakReference
- Object object = new Object();
- WeakReference<Object> sf = new WeakReference<Object>(object);
- object = null;
虚引用
1, 特点: 没有办法得到一个虚引用对象, 设置它的唯一目的就是在系统回收它的时候得到一个通知
2, 构造方式: PhantomReference
- Object object = new Object();
- PhantomReference<Object> sf = new PhantomReference<Object>(object);
- object = null;
垃圾收集算法
标记 - 清除算法
1, 过程
(1) 标记阶段: 程序会检查每个对象是否为活动对象, 如果是活动对象, 则程序会在对象头部打上标记.
(2) 清除阶段: 会进行对象回收并取消标志位, 另外, 还会判断回收后的分块与前一个空闲分块是否连续, 若连续, 会合并这两个分块.
回收对象就是把对象作为分块, 连接到被称为 "空闲链表" 的单向链表, 之后进行分配时只需要遍历这个空闲链表, 就可以找到分块.
在分配时, 程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block. 如果它找到的块等于 size, 会直接返回这个分块;
如果找到的块大于 size, 会将块分割成大小为 size 与 (block - size) 的两部分, 返回大小为 size 的分块, 并把大小为 (block - size) 的块返回给空闲链表.
2, 图解
3, 缺点
标记和清除的过程效率都不高
可能产生大量内存碎片
标记 - 整理算法
1, 过程
让所有存活的对象都向一端移动, 然后直接清理掉端边界以外的内存.
2, 图解
3, 缺点
每次都要移动存活的对象
复制算法
1, 过程
将内存划分为大小相等的两块, 每次只使用其中一块, 当这一块内存用完了就将还存活的对象复制到另一块上面, 然后再把使用过的内存空间进行一次清理.
现在的商业虚拟机都采用这种收集算法回收新生代, 但是并不是划分为大小相等的两块, 而是一块较大的 Eden 空间和两块较小的 Survivor 空间, 每次使用 Eden 和其中一块 Survivor. 在回收时, 将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上, 最后清理 Eden 和使用过的那一块 Survivor.
HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1, 保证了内存的利用率达到 90%. 如果每次回收有多于 10% 的对象存活, 那么一块 Survivor 就不够用了, 此时需要依赖于老年代进行空间分配担保, 也就是借用老年代的空间存储放不下的对象.
2, 图解
3, 缺点
每次只能用到局部的内存
分代收集算法
1, 按照对象生存周期将内存分为不同的区域主要分成新生代和老年代, 不同的区域采取不同的收集算法
2, 新生代: 采取复制算法
老年代: 采取标记 - 清理算法或者标记 - 整理算法
垃圾收集器
新生代收集器
Serial 收集器
ParNew 收集器
Parallel Scavenge 收集器
与 ParNew 一样是多线程收集器.
其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间, 而它的目标是达到一个可控制的吞吐量, 因此它被称为 "吞吐量优先" 收集器. 这里的吞吐量指 CPU 用于运行用户程序的时间占总时间的比值.
老年代收集器
Serial Old 收集器
Parallel Old 收集器
CMS 收集器
G1 收集器
搭配关系
比较总结
收集器 | 收集算法 | 新生代 / 老年代 | 备注 |
Serial | 复制算法 | 新生代 | |
ParNew | 复制算法 | 新生代 | |
Parallel Scavenge | 复制算法 | 新生代 | |
Serial Old | 标记 - 整理算法 | 老年代 | |
Parallel Old | 标记 - 整理算法 | 老年代 | |
CMS | 标记 - 清除算法 | 老年代 | |
G1 | 标记 - 整理算法 | 新生代 + 老年代 |
来源: https://www.cnblogs.com/huanglf714/p/11027336.html