引用计数法:如果一个对象被引用一次,则记录引用次数加一,如果引用取消,则减一,当减到0时,需要被回收。
问题:循环引用,A引用B,B引用A,除此之外,已经无法访问他们。
可达性分析算法:从GC根开始,找到GC根直接或间接引用的对象并标记,没有标记的便是需要回收的。
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 同步锁持有的变量
- 跨代引用的对象
标记-清除算法:从根开始遍历,将遍历到的对象做标记,没有做标记的被清除。
问题:清除之后存在内存碎片,严重影响内存利用率。
标记-复制算法:将内存按照比例分开,一部分闲置称A,另一部分存放对象称B。一次标记结束后,将存活的对象复制到闲置区域A,将B清空。
问题:浪费空间、当大量对存活时复制浪费的时间长(故老年代不使用该方法)。
标记-整理算法:将标记存活着的对象朝内存空间的一段移动,清除掉边界之外的对象。
问题:需要更新引用,操作复杂。
GC Roots大部分存在于栈帧的局部变量表中,而局部变量表中可能存放着一些基本数据类型和引用类型,如果要遍历全部来找出引用类型来作为GC Roots的话,效率过低。
当前主流的虚拟机都是准确式垃圾收集,也就是说,虚拟机可以直接知道栈中的变量是基本数据类型还是引用数据类型。
譬如内存中有一个32bit的整数123456,虚拟机可以直接判断出他是一个整数123456,还是它是指向123456的内存地址。
HotSpot是使用OopMap来解决这个问题,在即时编译的过程中,会在特定的位置记录下栈里的哪些地方是引用。在GC Roots枚举的时候,查找OopMap便能快速找到GC Roots了。
OopMap记录着引用的信息,如果每执行一次语句就更新一次OopMap,则会导致效率低下,所以虚拟机使用了一个叫安全点的技术,安全点中所有线程都将挂起,来方便OopMap更新和Gc Roots枚举,在安全点之外不会更新OopMap,OopMap累积到安全点再一次性更新。
虚拟机以“是否具有让程序长时间执行的特征”为标准选择安全点,准确的说就是:1. 方法调用 2. 循环跳转 3. 异常跳转。
抢先式中断:
虚拟机要垃圾回收的时候将全部线程中断,如果有线程不在安全点上,则让这个线程执行,让他也走到安全点。(如果这个时间实例化了个超大对象怎么办?)
主动式中断:
虚拟机不会主动中断线程,而是设置一个标志位,所有线程执行过程中不断轮询这个标志位,如果这个标志为真就在最近的安全点挂起,标志位和安全点是重合的,并且加上所有要创建对象和分配内存的地方(好像能解决上面这个问题了)。
有些线程Sleep,他们不能走到安全点挂起,其他线程也不能等他们醒来,所以设置安全区域,在安全区域内所有引用关系将不会变化,安全区域就是拉长的安全点。
上面提到GC Roots包含着跨代引用的对象,如果要搜集新生代,如何找到跨代引用的对象,莫非要遍历整个老年代?这个问题通过记忆集的技术来解决。
记忆集记录着从非收集区指向收集区域的指针的集合的数据结构。在垃圾回收的时候,便能通过记忆集来找出可以作为GC Root的跨代引用的对象。
记忆集有精读之分,有些能精确到具体地址,有些只能精确到一块内存区域,HotSpot就是精确到一块内存区域,这种记忆集称为卡表。HotSpot的卡表是个数组,数组中的每个元素对应一个内存块,称为卡页,如果一个卡页的值是1,则说明该内存块中包含着跨代指针,将他们加入GC Roots中一起扫描。
写屏障可以解决何时更新卡表的问题,写屏障简单而言是个AOP切面,当更新了引用时,会通过写屏障自动更新卡表信息。
GC Roots的枚举会暂停所有线程,而现在的许多收集器在标记过程中是不需要暂停线程的,可是并发标记会带来漏标和错标,一旦错标,将会导致程序正在使用对象被回收,导致程序崩溃,为解决此问题,引入三色标记来解决。
- 黑色:该对象已经被标记过了,且该对象下的属性也全部都被标记过了。(程序所需要的对象)
- 灰色:该对象已经被标记过了,但该对象下的属性没有全被标记完。(GC需要从此对象中去寻找垃圾)
- 白色:该对象没有被标记过。(对象垃圾)
当扫描结束后,所有非垃圾节点都变成了黑色,这时如果某个引用取消,则被引用的成垃圾,可仍然是黑色,属于漏标。
赋值器插入了一条或者多条从黑色对象到白色对象的新引用。
赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
为何需要条件1?
为何需要条件2?
增量更新:为了打破条件1。当赋值器插入了一条或者多条从黑色对象到白色对象的新引用时,将黑色对象变成灰色。
原始快照:为了打破条件2。赋值器删除了全部从灰色对象到该白色对象的直接或间接引用时,将改变前的引用关系快照保存,待并发扫描结束后,在扫描一遍改变前的快照(如果只满足了条件2,不满足条件1,这样做是不是会有可能产生浮动垃圾)。
用于老年代的收集器。
运作过程:
G1收集器面向整个堆内存进行回收,衡量标准不是分代,而是将堆内存分为等大的Region,哪个Region的回收价值高,回收那个Region。
原文:https://www.cnblogs.com/starcloud/p/14998819.html