JVM:这是一份详细的(GC)垃圾回收算法讲解
程序员文章站
2022-06-19 10:50:41
...
第一部分:判断Java对象是否存活
在讲解(GC)垃圾回收算法之前,你必须了解的知识:如何判断一个Java对象是否存活
1. 判断方法
- 垃圾收集器对java堆对象是否回收的判断准则是:java对象是否存活
判断对象为死亡时才会进行回收
- 在java虚拟机中,判断对象是否存活的2中方法
- 引用计数法
- 可达性分析
2. 引用计数法
2.1 方法描述
- 给每个Java对象添加一个引用计数器
- 只要有一个地方引用该对象,则计数器+1,当引用失效是-1;
- 当计数器为0时,判断该对象死亡,可以进行垃圾回收
2.2 优点
- 实现简单
- 判断高效
2.3 缺点
- 存在对象之间相互循环引用的问题
- 具体描述
//有两个对象,stuA和stuB,都有字段name
//这两个对象相互引用,无其他任何对象引用他们
stuA.name = stuB;
stuB.name = stuA;
实际上它们都应该被垃圾回收器回收,但是因为引用计数器还未归0,所以不能被垃圾回收器进行回收,就造成了内存泄漏
3. 可达性分析
3.1 方法描述
- 从GC Roots触发,向下搜索寻找到一条路径到达java对象
- GC Roots可以是
- Java虚拟机栈中引用的对象
- 本地方法栈中JNI引用对象
- 方法区中常量
- 类静态属性引用的对象
- 如果找不到一条从GC Roots到该Java对象的路径,则代表该Java对象“暂时死亡”
需要注意
- 当判断该Java对象到GC Roots没有路径时,还未代表该Java对象“真正死亡”
- 需要判断一个Java对象真正死亡,需要进行两个阶段,两次标记和筛选
3.1.1 第一次标记和筛选
- 在对象被可达性分析中判断为不可达对象时,进入第一次标记和筛选阶段(在即将回收集合中)
- 如果该对象没有finalize()方法或者finalize()方法已经被虚拟机调用过,则判断该对象真正死亡
- 否则,该对象进入第二次标记和筛选阶段
3.1.2 第二次标记和筛选
- 对象进入F-Queue队列中
- 执行finalize()方法
- 如果在执行finalize()方法后,对象依然没有和GC Roots有直接和间接关联,就判断该对象真正死亡
- 否则,判断该对象仍然存活
3.2 优点
- 解决对象间相互循环引用的逻辑问题
第二部分:(GC)垃圾收集算法
1. 垃圾收集算法类型
- 标记-清除算法
- 标记-整理算法
- 复制算法
- 分代收集算法
最主要的是了解标记整理算法和复制算法的原理,现在主流的垃圾收集算法虽然是分代收集法,但是分代收集算法只是根据新生代和老生代来使用复制算法和标记-整理算法。
2. 标记-清除算法
2.1 算法描述
分为两个阶段:
- 标记阶段:标记出所有已死亡的对象
- 清除阶段:清除所有被标记的对象
2.2 优点
算法简单、实现简单
2.3 缺点
- 清除死亡对象后,会产生大量不连续的内存空间
- 标记和清除阶段效率不高
这会导致以后抽象需要进行分配较大空间的对象无法找到足够的空间进行分配,而*触发另一次的gc
3. 标记-整理算法
3.1 算法描述
- 标记阶段:标记出仍存活的对象
- 整理阶段:让所有存活的对象向一端先移动,然后统一清除死亡对象
3.2 优点
- 解决了标记-清除算法产生不连续内存碎片的问题
4. 复制算法
4.1 算法描述
- 将内存空间分为大小相等的两块,每次只使用其中一块
- 当使用的这块内存不足时,就将这块内存上还存活的对象复制到另一块没有使用的内存当中
- 然后将需要清理的那块内存全部清理干净
4.2 优点
- 解决标记-清除算法中空间产生不连续内存碎片的问题
4.3 缺点
- 每次使用的内存只有原来的一半
- 当存活率较高的时候,需要做很多复制操作,就会降低效率
5. 分代收集算法
5.1 算法描述
- 将Java堆分为:新生代 老生代
- 新生代采用复制算法清除:因为新生代对象存活率低,容易执行gc算法
- 老生代采用标记-清除和标记-整理(主要)算法:因为老生代存活率高,不容易执行gc算法,对内存空间要求更严格
- 新生代的对象,每经过一次gc仍然留下来,就给它年龄+1,当年龄达到一定程度,将该对象放入老生代空间
5.2 优点
- 效率高,空间利用率也高
针对不同区域的特点,使用不同的垃圾收集算法
上一篇: position和BFC