概述
相关知识结构
GC的大致流程
- 判断对象是否可回收(是否死亡)
- 在合适的时候由具体的
垃圾收集器
执行回收操作
哪些内存需要回收?
堆内存的回收
1. 引用计数算法
给对象中添加一个引用计数器,每当有一个点引用它时,计数器的值就加1,当引用失效时,计数器减1。
优点:
- 实现简单
- 高效
缺陷:
- 无法解决循环引用的问题
循环引用:两个对象已经无法被访问,但是却相互引用着对方,导致他们的引用计数器都不为0,无法通知
GC
收集器回收他们。
2. 可达性分析算法
将一些列GC Roots
的对象作为起始节点(树的根节点),不断向下搜索,走过的路径就称为引用链(Reference Chain)
,当一个对象到GC Roots
没有任何引用链相连的时候(即:从GC Roots
不可达),则该对象是不可用的,将被回收。
可作为GC Roots
的对象:
- 虚拟机栈帧中本地变量表中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量哦=引用的对象
- 本地方法栈中
Native方法
引用的对象
优点:
- 解决了引用计数算法中循环依赖的问题
缺点:
- 每次回收前,需要先确定
GC ROOts
3. 四种引用类型
无论是引用计数算法
还是可达性分析算法
,都离不开“引用”。
强引用:只要引用还在,对象就永远不会被回收
- 是
Java
程序中普遍存在的一种引用 - 通过类似:
Object obj = new Object();
的操作生成的引用。
软引用:内存不够时就会被回收
- 用来描述一些还有用但非必要的对象。
JDK 1.2
之后,通过SoftReference
类来实现软引用。
弱引用:下一次垃圾回收时就会被回收
- 也是用来描述非必须的对象,但是强度较 软引用 更弱。
JDK 1.2
之后,通过WeakReference
类来实现弱引用。
虚引用:不会对对象的生存时间构成影响
- 无法通过虚引用获取一个对象实例。
- 唯一目的:在对象被回收时收到一个系统通知。
JDK 1.2
之后,通过PhantomReference
类来实现虚引用。
方法区的回收
常量池的回收
不存在相关引用即可回收。
类信息的回收
要同时满足一下三个条件:
- 类的所有实例都被回收(堆中不存在任何实例)
- 加载该类的
ClassLoader
已被回收 - 不存在该类的
java.lang.Class
对象的引用
如何回收
标记-清除 Mark-Sweep
通过对可回收的对象进行标记,再统一进行回收
优点:
- 简单
缺点:
- 效率低:标记和清除两个过程效率都不高
- 会产生大量的内存碎片
内存碎片:小而不连续的内存空间。内存碎片过多会导致较大对象分配时可用内存不足,不得不提前触发下一次垃圾回收,影响性能。
复制 Coping
每次只使用一半的内存区域,在触发回收时,将可用的数据复制到另一半空间中,并对当前这一半进行回收。
优点:
- 实现简单:不用考虑内存碎片等问题、只需要移动对顶指针按序分配内存即可
- 运行高效
缺点:
- 代价较大:内存缩小了一半
- 存活率较高时,需要进行较多的复制操作,影响效率
标记-整理
通过对可回收的对象进行标记,将后面可用的对象整理到当前位置。
即:让所有的可用对象向前移动,紧凑到一块连续的空间中。
优点:
- 不会有额外的空间浪费
- 不产生内存碎片
缺点:
- 效率较低
分代收集
根据对象的存活周期,将内存划分为几个不同的块,在每一个块中使用不同的垃圾回收算法。
是当前商业虚拟都采用一种算法。
新生代
- 对象存活时间较短的内存区域,每次回收时都有大批对象死去
- 一般使用 复制算法 ,只需付出少量存活对象的复制成本就可以完成收集
- 分为
Eden
、From Survivor
、To Survivor
三部分,其带小比例一般为8:1:1
:
①:Eden
和From Survivor
复制到To Survivor
中
②:From Survivor
与To Survivor
交换
即To Survivor
变为From Survivor
,From Survivor
变为To Survivor
老年代
- 对象存活率较高的内存区域。
- 一般使用 标记-整理 或者 标记-清除 算法,可以不适用额外的空间进行分配担保。
什么时候回收
不同的垃圾收集器有不同的实现。
对于部分的收集器,收集器无法与工作线程同时运行,即存在 “Stop The World” 现象:所有工作线程都暂停,直至垃圾回收结束。
可以与工作线程同时运行的收集器:
- CMS
- G1