如何确定垃圾
1.引用计数法。
原理很简单,因为java对象使用地址引用来互相调用的,就可以使用引用计数来计算这个对象是否被使用,如果没有被引用,就可以被回收。
缺陷: 无法解决循环依赖的问题,例如a调用b,b调用a,即使这两个对象已经没人使用也不会被标记为垃圾。所以目前java不使用这种算法来进行垃圾处理。
2.可达性分析法。
java会通过一系列的源对象gc root,来判断某个对象是否通过一连串的调用可达。如果不能通过gc root来到达某个对象就判断这个对象不可达,一个对象被标记最少两次不可达之后就会被标记成垃圾。
需要注意的是可以作为root的对象有以下几类
1. java虚拟机栈中的对象
2. 元数据空间中类静态属性引用的对象
3. 元空间运行时,常量池中常量引用的对象
4. 本地方法栈中native方法引用的对象。
可达性分析法需要在保证一致性的快照中进行,否则结果可能不准确。
为什么最少标记两次才会被判定回收。
1. 一个对象,首先要经过一次可达性分析,来判断是否可达,如果不可达,就会被标记一次,
2.接下来会对被标记过一次的对象进行判断,是否有必要执行该对象的finalize()方法,如果没有覆盖或者已经执行过一次finalize()则没有必要执行,被标记可回收。被判断有必要执行finalize()方法的对象会被加入F-query队列。
3. 虚拟机自动建立一个Finalizer线程去执行F-query队列里的对象的finalize方法,
4. 执行完毕后,由回收器开始二次标记,在Finalizer线程执行过程中如果能与调用链的任何对象产生关联,则之后会被回收器移除F-query不会被回收,执行失败或者执行后位于调用链的对象产生关联的,会被二次标记,
自救的机会只有一次,因为finalize()只会执行一次,而且在执行过程中只保证执行,不保证成功,有可能会执行失败,失败了也会被回收。比如执行过程中内存严重不足,会被直接释放,慢了会导致内存溢出。程序停止。
stw(stop the world)
世界停止,对于java来说就是将用户线程停止,可达性分析需要将所有的用户线程停止,让内存呈现快照的状态。来保证一致性和减少浮动垃圾的产生。如果不停止用户线程,对象分析前后不一致,就可能导致结果不准确。比如a检测时是可达,检测完毕,调用对象a的对象将a抛弃,a就变成了不可达。这样检测结果就不准。a是垃圾,但是却没有检测到,变成了浮动垃圾。
垃圾回收算法
1,标记清除法
顾名思义,就是先通过可达性分析判断对象是否是垃圾,如果是则标记,然后在清除阶段清除,回收垃圾对象所占用的空间。
缺陷:垃圾对象的位置无法控制,所以回收出来的内存可能会不连续,会有很多碎片内存,当给新对象分配内存时,单独的碎片内存无法满足新对象的使用,就会再次触发gc。
2,复制算法
它将内存划分成大小相同的两块AB,每次只使用其中一块A。在gc的时候,将存活的对象,紧凑的复制到另一块B中,然后将A中的垃圾对象清除,回收空间,然后AB互换角色。这样a中就清空了垃圾,b中的空间也很紧凑,解决了标记清除算法碎片内存的问题。
缺陷:内存使用率很低,总有一半的内存未被使用。如果A中存活的对象数量很多。复制也是一笔很大的开销。
在现代高性能虚拟机中。复制算法常用来处理对象新建和消亡比较快的新生代。它将新生代划分成三个区域。eden ,survivorTo,survivorFrom。大小比例默认为8:1:1。每次只使用其中的两块,eden和from。到垃圾处理时,将存活的对象复制到to中。然后to和from角色互换。
存在to内存不够的情况,这个时候,就由空间分配担保机制,提前将符合条件的对象,从to中晋升至老年代。确保新生代有足够的空间来保证新生代gc正常工作。
由此复制算法的缺陷,由一半的空间损耗,降至百分之10
3,标记整理算法-压缩算法。
标记阶段标记存活的对象,整理阶段将存活的对象移动到内存的一端。按照地址顺序排列。然后回收掉对象边界以外的内存。
优点是:空间利用率比复制算法高,而且没有内存碎片的问题,分配新对象时,只需要持有内存的起始地址就可以了。
缺点: 效率比复制算法低。