JVM垃圾收集

  • 对象已死吗?

Java堆几乎存放了所有的对象实例,垃圾回收器在进行回收之前,首先确定的就是哪些对象存活,哪些对象死亡。

  • 可达性分析算法:

由于引用计数算法无法处理“循环引用”的情况,所以主流的商用程序语言(Java、C#等)都是通过可达性分析来判定对象是否存活的。

这个算法的基本思想就是:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到CG Roots没有任何引用链相连时,则证明此对象时不可用的。如上图

在Java语言中,可以作为GC Roots的对象包括下面几种:

    1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    2. 方法区中类静态属性引用的对象。
    3. 方法区中常量引用的对象。
    4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。
  • 回收方法区:

永久代的垃圾收集主要回收两部分内容:

      • 废弃常量

假如一个字符串“abc”进入了常量池中,但是当前系统没有任何一个String对象是叫做“abc”的,换句话说,就是没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用了这个字面量,如果此时发生内存回收,而且必要的话,这个“abc”会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用与此类似。

      • 无用的类(需要同时满足以下三点)

1.该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。

2.加载该类的ClassLoader已经被回收。

3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

 

 

  • 垃圾收集算法
  • 标记清除算法(Mark-Sweep)

分为“标记”和“清除”两个阶段:首先标出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它的不足之处主要有两个:一个是效率问题,标记和清除两个过程效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

  • 复制算法(Copying)

为了解决效率问题,复制算法出现了,它将可用的容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次是对半个区域进行内存回收,内存分配的时候也不用考虑内存碎片等复杂情况,只要移动指针,按顺序分配即可。只是这种算法的代价过高,需要牺牲掉一半的内存。但是值得注意的是:现在的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden和两块较小的Survivor空间,每次使用Eden和其中一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。

  • 标记整理算法(Mark-Compact)

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会贬低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以对应被使用的内存中所有对象都存活的极端情况,所以在老年代一般不能直接采用这种算法。根据老年代的特点,有人提出了“标记-整理”算法,对于可回收对象不是直接清理,而是让存活的对象都向一段移动,然后直接清理掉边界以外的内存。

  • 分代收集算法(Generational Collection)

根据对象存活周期的不同,将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,那就选用复制算法。而老年代中因为对象存活率较高、没有额外空间对他进行分配担保,就必须使用“标记-清理”或“标记-整理”算法进行回收。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2019-09-09 18:25  zty-lyq  阅读(116)  评论(0编辑  收藏  举报