这是我参与「第五届青训营 」伴学笔记创作活动的第5天。
主要是记录视频当中自动内存管理的知识点。
一、自动内存管理
自动内存管理(垃圾回收),是由程序语言的运行来帮助我们管理动态内存。这样我们不需要去手动管理内存,只需要专注于业务逻辑的实现。而且它也能保证内存使用的正确性和安全性。
垃圾回收(GC)主要任务
1.为新对象分配空间
2.找到存活对象(指后续程序还需要再调用的对象)
3.回收死亡对象的内存空间(因为该对象后续不再调用,该空间不如将其闲置)
二、相关概念
两种线程分类
Mutator:业务线程,分配新对象,修改对象指向关系。也就是用户启动的线程。
Collector:GC线程,找到存活对象,回收死亡对象的内存空间。用于处理垃圾空间的线程。
GC算法
Serial GC:指所有业务线程运行到一半的时候,程序告知要进行垃圾回收操作,则把所有业务线程全部暂停,使用一个GC线程完成工作。垃圾回收完成以后,再启动业务线程。
Parallel GC:与Serial GC一样,进行垃圾回收操作时需要暂停所有业务线程,等垃圾回收操作完成再启动业务线程,但是Parallel GC能够启动多个GC线程,相比第一个算法运行效率更高。
Concurrent GC:该算法不需要暂停所有业务线程,只需要对需要进行垃圾回收的线程暂停操作,其他线程仍然正常运行。该线程的难点在于,垃圾回收的过程当中,其他用户正在使用的线程可能会有新的对象产生,但是该对象肯定是有需求的,必须保留不能被当作死亡空间回收。因此GC线程也要感知对象指向的其他关系发生的改变信息。
三、GC算法评价指标
安全性:不能回收存活对象。(这也导致业务线程和GC线程同时进行的Concurrent算法比较复杂,因为容易有新的存活对象产生,可能被误回收)
吞吐率:1-GC时间/程序执行总时间,检测是否在GC上花费过多时间。
暂停时间:stop the world。主要是前两种GC算法需要暂停所有业务进行垃圾回收操作,如果业务长时间不进行操作的话就能感知到有暂停发生。因此暂停时间越短越好。
内存开销:GC元数据开销,开辟额外空间进行内存管理。
四、两种GC算法
1.追踪垃圾回收
对象被回收条件:指针指向关系不可达的对象
如下图所示,图中Global Variables指全局变量,Stacks指线程栈,Heap指检验是否要回收对象的区域。
首先对死亡对象和存活对象进行分类。将所有静态变量、全局变量、常量、线程栈等会被标记根对象,这些对象一定不会被丢弃的。然后从这些根对象开始,它的指针能够被指向的对象,将会作为存活对象保留,而这些存活对象能够指向的其他对象,也将视为存活对象,依次循环。只要能被根对象一级一级指针指向的对象,将被作为存活对象保留,这里会被标注为黑色,其它无法有通路能够指向的对象,会被视为死亡对象,将对其进行清理工作。
清理工作有以下三种方法,会根据实际情况使用不同的清理策略
(1).将存活对象保存到其它内存空间,之前存在的内存空间被视为空闲区域。
(2).将死亡对象的内存标记为可分配空间,后续进入的对象可以再次占用这些可分配空间
(3).移动并整理存活对象,将存活对象全部拷贝到该空间刚开始的位置,剩下的空间视为空闲区域。
2.引用计数
对象被回收条件:当对象被标记的引用数为0时。
如下图所示,每个对象都会标记一个与之关联的引用数目。
举图中的例子 红圈圈出的对象有两个箭头指向它,一个是左边的标记为“1”的对象,一个是右边的“GC ROOTS”,有两个对象指向,因此引用数为2。
蓝圈圈出的对象没有箭头指向它,因此引用数为0,需要被内存回收。
绿圈圈出的对象虽然有一个箭头指向它。但是指向绿圈的单位引用数为0,该对象要被内存回收,该对象被回收后就没有对象指向绿圈,则绿圈圈出的对象的内存空间也要被回收。
优点:
(1).内存管理操作平摊到执行过程
(2).内存管理不需要了解实际运行细节
缺点:
(1).维护引用计数开销较大,带有引用计数操作比较复杂
(2).无法回收环形数据结构(即多个对象呈环形指向,如下图所示。那么每个对象引用计数为1,就无法回收)
(3).内存开销大,引用额外的内存空间记录引用计数
(4).回收内存可能引发暂停
总结
难