JVM(二)JVM垃圾回收

在讨论JVM垃圾回收之前先要确定,JVM内存中哪些内存区域会进行GC。

哪些内存区域需要GC

在JVM的5个内存区域中,程序计数器、虚拟机栈、本地方法栈时随线程而生,随线程而死。

程序计数器记录下一条指令的行号,自然不能被回收。

虚拟机栈和本地方法栈都是栈内存,只是所服务的对象不同,栈内存中的栈帧随着方法的调用返回而不断进行着出栈入栈操作,方法入栈之前其所需的内存是已知的,在方法执行期间不会改变,而在方法执行结束之后会随着出栈操作被回收。所以栈内存的分配和回收具有确定性,因此不需要考虑额外的回收操作。

堆内存中存放的是程序动态生成的对象实例,具有不确定性。因此堆内存需要垃圾回收器来回收对象,包括java堆和方法区。

怎么判断对象需要被回收

  1. 引用计数法
    引用计数是最简单的一种回收算法,当对象被别的对象引用时引用计数器+1;引用失效时计数器-1。当引用计数为0时回收对象内存。
    但是这种算法无法解决对象之间的互相引用问题

  2. 可达性分析算法
    此算法通过一系列称为“GC Roots” 的对象为起始点,从这些起点开始沿着引用搜索。当一个对象和任何一个GC Root对象之间都没有引用链联系时,证明此对象时不可用的,就会被回收。
    GC root对象包括:

    1. 虚拟机栈中引用的对象,就是栈帧中的本地变量表中的对象
    2. 方法区中类静态属性引用的对象
    3. 方法区中常量引用的对象。如引用某常量字符串的对象
    4. 本地方法栈中引用的对象
      GC root不包括java堆中对象所引用的对象,以避免出现循环引用。

垃圾回收算法

1. 标记-清除算法

此算法分为两个阶段:标记和清除。
这种算法的不足有两个:
1. 效率低
不论是标记还是清除,都要循环遍历,效率不高
2. 内存利用率低
因为只管清除内存,不管整理,因此易产生内存碎片,导致没有足够大的内存块而必须提前触发另一次的垃圾回收动作。

2. 复制算法

此算法把内存划分为相等的两块,当其中一块内存用完后将此内存块中可用的对象全部复制到另一块,然后一次性地清除此块内存。

优点是:实现简单,效率高,不用考虑内存碎片问题。
缺点是:将内存划分为两块,相当于只使用一般的内存,空间代价高。

HotSpot虚拟机将堆内存划分为新生代和老年代。并在新生代使用此算法。由于新生代对象变化快,所以内存的划分不是1:1,而是将内存划分为一块较大的Eden空间,和两块较小的Survivor空间。每次使用Eden和其中一个Survivor区。当回收时,将Eden和Survivor中存活的对象一次性复制到另一个Survivor区域,然后清除刚才用过的内存。如果另一块Survivor没有足够的空间放入存活对象,则通过分配担保机制将对象放入老年代(具体参考内存分配策略

3. 标记-整理算法

标记-整理算法与 “标记-清除” 算法类似,先标记处需要回收的对象,然后将所有存活的对象移动到一端集中起来,再清理掉端边界以外的内存,这样清理过后的内存所有存活对象集中在一起,没有内存碎片问题。

4. 分代收集算法

当前商业VM都采用此算法。根据对象的存活周期不同将内存划分为几块。
一般把java堆划分为新生代和老年代,新生代的对象每次GC时都有大量对象死去,因此在新生代选择复制算法;老年代对象存活率高,使用“标记-清理”或者“标记-整理”算法进行回收。

猜你喜欢

转载自blog.csdn.net/wy11933/article/details/80210260