目录
一、GC 是什么?
二、GC 要回收哪部分内存?
三、回收的基本单位
四、回收对象的基本思路【重点】
(1)标记
(2)回收
五、什么是分代回收?
六、一个对象的一生
七、一些专业术语
一、GC 是什么?
内存是很宝贵的资源,为了更好的回收内存,得保证内存在用的时候能被申请,不用的时候被释放。释放内存比较麻烦,很容易遗漏或者释放时机过早,为了避免这些问题,引入了垃圾回收算法,能够很大程度上的避免内存泄露。
二、GC 要回收哪部分内存?
堆 主要回收的地方
方法区 GC需要回收方法区的内存!! 但是方法区空间小,数据失去作用的概率低。
栈 不需要回收,栈上的内存何时释放时机是明确的 (线程结束,栈上的内存就全部被释放了,某个栈帧销毁,也会导致对应的局部变量被释放) (栈帧销毁的时机:对应的方法执行完)
程序计数器 :只是存放地址,不需要回收。
综上:回收以堆为主,方法区为次。
三、回收的基本单位
内存的单位是”字节“,回收内存就是按照字节来回收吗?并不是,而是按照 对象 的方式来回收。
每个对象,都持有了一定的内存,释放对应的对象,也就回收了对应的内存。
堆内存分成三个部分:正在使用,已经用过(主要回收),尚未使用,回收以对象为单位
四、回收对象的基本思路【重点】
先标记,再回收。
(1)标记
a)引用计数:存在循环引用的问题(循环引用的情况下就无法释放了)
记录当前这个对象,是否有引用指向(有几个引用)
每个对象都专门分配一个计数器变量,有新的引用指向该对象,引用计数 + 1,有旧的引用指向别的对象了,引用计数 - 1,当引用计数为0,表示该对象没有引用了(没有引用也就无法再代码中被使用)于是这个对象就可以被回收了
当某个对象引用计数为0的时候,引用计数为0的时候,就意味着没有引用指向该对象了,于是该对象就可以被释放了(可以视为是垃圾了)
b)可达性分析:把对象之间的引用关系视为一个有向图,遍历这个图,只要能被访问到的对象,就不是垃圾,反之遍历不到的对象就是垃圾,遍历的入口就是GCRoot
b1)每个 栈帧上的局部变量表
b2)常量池中引用指向的对象
b3)静态变量引用的对象
c)关于方法区的类”卸载“规则
c1)该类的所有实例都被回收了
c2)该类的类加载器也被回收了
c3)该类的类对象没有被其他代码使用
同时满足这三点,类才能被卸载
(2)回收
回收:把死了的对象回收回去。
回收的算法:
a)标记-清除【适合老年代】 针对被标记的垃圾对象,直接删除
两个空闲内存是被其他对象分割开的,一旦需要申请一个比较大的连续空间,就会申请失败,
如果内存碎片很多,就可能导致虽然整体的内存空间剩余很多,但是不连续,也就无法创建比较大的对象/数组。
如果代码中频繁创建,频繁释放,就特别容易产生大量的内存碎片。
优点:简单高效
缺点:造成内存碎片
b)标记-复制【适合新生代】 需要另外一块也很大的内存区域,把存活的对象直接拷贝到另外的内存区域中,再整体释放原来的区域。
把不是垃圾的对象,拷贝到内存区域的另外一边,然后整体释放左侧的内存区域
【适合新生代】 新生代中的对象存活时间端,每次GC 扫描都认为只有少数对象能保留
优点:没有内存碎片。
缺点:需要更大的额外空间,如果存活的对象多,也比较低效。
c)标记-整理【适合老年代】 类似于线性表删除中间元素,会触发内存搬运动作。
优点:没有内存碎片,也不需要额外的空间。
缺点:搬运动作相对低效,不太适合频繁操作。
五、什么是分代回收?
划分依据:根据对象的年龄(经过 GC 扫描的轮次) 把堆分成
新生代:伊甸区 + 生存去 * 2
老年代
六、一个对象的一生
a)诞生于伊甸区
b)经过一轮 GC,如果存活,就通过复制算法,进入生活区
c)在生存区中经过 GC 后存活,还是通过复制算法,进入生存区2(每次经过 GC 扫描,生存的对象都要被复制到另外一个生存区)
d)在生存区中活过一定的轮次之后,对象将被放到老年代(认为该对象生存时间会较长,就不会继续频繁扫描了)
e)老年代的对象也似乎需要进行 GC 的,扫描轮次没有新生代那么频繁了
七、一些专业术语
一些相关术语的补充:
1.Particial GC:只进行一部分内存区域的 GC
2.Full GC:针对整个内存区域的 GC
3.Minor GC:针对新生代内存的 GC,执行频繁,速度比较快
4.Major GC: 针对老年代的 GC,执行没那么频繁,速度比较慢【通常是由 Minor 触发的】
Full GC = Minor GC + Major GC
由于Major GC 通常由 Minor GC 触发的,所以也有的时候使用
Major GC 代指 Full GC



