垃圾 -> 程序不用的内存空间
gc要做什么 -> 找到垃圾,回收垃圾
为什么要回收 -> 因为只分配不释放会内存泄漏,占满内存,系统崩溃
释放需要注意什么 -> 正确释放 -> 防止悬垂指针(指向释放完毕的空间)/防止释放正在使用的空间
GC操作的基本单位是对象,对象包含对象头(对象的大小种类,根据不同gc算法所需的信息)和域(对象使用者可以引用或替换)
gc分配堆内存空间 -> mutator要求在堆中存放对象 -> 堆满进行gc -> 如果还不够则尝试增大堆
根 (全局变量,寄存器,调用栈)
gc评价标准: 吞吐量(单位时间的处理能力,比如复制算法只检查活动对象,标记清除算法检查所有活动和非活动对象,随着对象类型占比不通则效率不通),最大暂停时间(因为执行gc而导致的暂停执行的最长时间),堆使用率(复制能用一般,标记清除都能用),访问局部性(为了更好的利用存储器,寄存器->缓存->内存->辅助存储,将更有用的数据加载到更小但更快的存储中使用,用临机预读,引用关系对象预加载等方式提升)
标记清除
标记所有活动对象(所以标记花费的时间与活动对象的总数成正比,采用深度优先遍历),清除那些没有标记的非活动对象(遍历所有堆,所以堆越大,清除花费的时间越长,将非活动对象作为分块,连接到空闲列表,之后遍历空闲链表就可以找到可用分块了),分配(将回收的垃圾进行再利用,搜索空闲链表并寻找合适大小的分块-或者找到最合适的,或者找到第一个能装下的,最差不可用的是找最大的)
合并:在清除阶段将连续的小分块连接在一起形成一个大的分块
缺点:碎片化,分块不连续,遍历空闲链表时间不确定,可能在最头也可能在最后,因为每次都要给对象头写标识所以根写时复制不兼容
措施:多个大小不同的空闲链表,用空闲链表数据管理,或将大小相近的对象整理成固定大小的块进行管理,位图标记法,延迟清除只在分配时执行必要的遍历
引用计数器
在对象头中记录有多少程序应用了这个对象,在mutator的处理过程中通过增减计数器的值来进行内存管理,更新引用的时候,先对新引用对象的计数器+1,再对之前引用的对象-1,再更新引用关系,先+1再-1是为了防止同一个对象的时候-1被回收了就没办法+1了。
优点:
立即回收:引用计数算法中会监督在更新指针的时候是否产生垃圾,从而在产生垃圾的时候立即回收,而标记清除即使产生了算法也不立即回收,而是只会在没有分块的时候将垃圾一并回收
最大暂停时间短:只有当通过mutator更新指针时程序才会回收垃圾
没有必要沿指针查找
缺点:计数器值的增减处理繁重,计数器需要占用很多位 空间利用率下降,实现繁琐,循环引用无法回收
升级:
延迟引用计数器:从根引用的指针的变化不反映在计数器上,使用zct表,事先记录下计数器值在def_ref_cnt函数作用下变为0的对象。如果没空间了再scan_zct把所有通过根直接引用的对象的计数器都进行增量,反应到计数器值上
优点是延迟了根引用计数器的技术,将垃圾一并回收,减轻了根引用频繁变化而导致的计数器增减锁带来的额外负担
缺点是引用计数器的增减有延迟,垃圾无法马上回收
strcky:
减少计数器位宽,
1位引用计数法
部分标记清除算法