JVM垃圾回收机制
程序员文章站
2022-06-05 13:33:53
...
JVM垃圾回收机制
回收步骤
1.发现无用信息对象
2.回收被无用对象占用的空间
问题
1.哪些是无用对象
2.什么时候回收
3.如何回收
垃圾标记算法
回收对象:堆中的对象
1.引用计数法
- 1.1 定义
- 堆中的每一个对象都有一个引用计数。当一个对象被创建时,自动给该对象分配一个变量,该变量计数设置为1。每当有一个地方引用它,计数器的值就加1;当引用失效(包括引用超过了生命周期和引用指向新对象)时,计数器的值就减1;任何时刻计数器为0的对象就是不可使用的。
- 1.2 存在的问题
-
无法解决对象之间的相互循环引用的问题。
假如两个不在被任何引用的对象,因为在其属性中相互引用对象,那么即使这两个对象已经废弃,也因为和对方的相互引用而无法被回收。
或者如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
/*
最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。
*/
2.可达性分析算法
- 2.1 算法思想
- 通过一系列的称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路程称为引用链(Reference Chain),当一个对象到”GC Roots”没有任何引用链相连(即不可达)则证明该对象是不可用的.
程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
- 2.2 可以作为GC Roots的对象
-
java中可作为GC Root的对象有
1.虚拟机栈中引用的对象(本地变量表)
2.方法区中静态属性引用的对象
3. 方法区中常量引用的对象
4.本地方法栈中引用的对象(Native对象)
- 2.3 宣告对象死亡的两个标记过程
- 1.如果对象在进行过可达性分析后发现没有与GC Roots相连接的引用链,那么他将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finaliz()方法.如果该对象没有覆盖finaliz()方法或者finaliz()方法已经被虚拟机调用过,虚拟将这两种情况视为”没有必要执行”;
2.如果该对象被判定为有必要执行finaliz()方法,那么finaliz()方法将是对象逃脱死亡命运的最后一个机会,如果对象需要在finaliz()方法中拯救自己,就需要重新与引用链上的任何一个对象建立关联即可,否则将被GC进行第二次标记.
垃圾收集算法
1.标记-清除算法
- 1.1 算法思想
-
如同它的名字一样,算法分为”标记”和”清除”两个阶段
- 首先标记出需要回收的对象
- 在标记完成后统一回收所有被标记的对象,标记的过程也就是可达性分析算法的过程
- 1.2 存在的问题
- 1.效率问题: 标记和清除的两个过程的效率都不高;
2.空间问题:标记会产生大量的不连续的内存碎片,空间碎片太多可能会导致以后在程序运行的运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作.
2.标记-整理算法
- 2.1 算法思想
- (解决”标记-清除”算法的空间问题) 算法的标记过程与”标记-清除”算法一样,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。
- 2.2 算法流程
3.复制算法
- 3.1 算法思想
- (解决”标记-清除”算法的效率问题)它将可用内存按容量划分成大小相等的两块,每次只使用其中的一块.当着一块的内存用完了,就将还存活(更准确的说是未标记的)的对象复制到另一块上,然后再把已经使用过的内存空间一次清理掉.
这样每次只需要对整个半区进行内存回收,内存分配时也无需考虑到内存随便的复杂情况,只需要移动堆顶指针,按顺序分配内存.
提高了运行效率
3.2 优点和缺点 - 优点:实现简单,运行高效
缺点: 代价是将内存缩小为了原来的一半. - 3.3 改进算法
- 由于新生代的98%的对象都是朝生夕死,所以无需按照1:1的比例来进行划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor,最后清理掉Eden和刚才用过的Survivor空间.虚拟机默认Eden和Survivor的大小比例8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%).
但是并不能每次保证都有98%的对象可回收,当Survivor的空间不够用的时候需要依赖其它内存(这里指老年代)进行分配担保.
如果另一块Survivor没有足够空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代.
4.分代收集算法
当前的商业虚拟机都采用"分代收集算法",即根据对象存活周期的不同将内存划分为几块.一般是把堆分成新生代和老年代.
在新生代中,因为每次垃圾收集时都发现有大批对象死去,只有少量存活,所以采用"复制算法".只需要付出少量存活对象的复制成本就可以完成收集.
而老年代中因为对象存活率高,没有额外空间对他进行分配担保,就必须使用"标记-清除"或者"标记-整理"算法来进行回收.