Fork me on GitHub

【JVM】垃圾回收机制

垃圾回收机制

1. 对象存活判定算法

1.1 引用计数法

算法思路:给对象添加一个引用计数器,当有一个地方引用它时,计数器+1;当引用失效时,计数器 -1;任何时刻计数器为0的对象是不能再被使用的

无法解决相互引用的问题

1.2 可达性分析算法

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

GC Roots对象:

  • 虚拟机栈(栈帧中本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • JNI中引用的对象

1.3 引用类别

  • 强引用:我们常见的普通对象引用,类似"Object obj = new Object()"这类引用,只要还有强引用指向一个对象,就表明该对象还活着,GC永不会回收该对象
  • 软引用(SoftReference):相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当JVM认为内存不足时,才会去试图回收软引用指向的对象。软引用通常用来实现内存敏感的缓存。
  • 弱引用(WeakReference):也是用来描述非必须对象,强度比软引用更弱一些,被弱引用关联的对象只能生存到下次垃圾收集发生之前。无论当前内存是否足够,都会回收掉只被弱引用关联的对象。同样是很多缓存实现的选择
  • 虚引用(PhantomReference):有时也叫幻象引用,最弱的一种引用关系,不能通过它访问对象。虚引用仅仅提供一种确保对象被finalize以后,做某些事情的机制,如Post-Mortem清理机制、监控对象的创建和销毁、Java平台自身的Cleaner机制。

1.4 方法区垃圾回收

  • 废弃常量:系统中没有任何地方引用该字面量
  • 无用的类
    • 该类所有实例都已被回收
    • 加载该类的ClassLoader已经被回收
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法

2. 垃圾收集算法

2.1 标记-清除算法

算法分标记清除两个阶段,首先标记处所有需要被回收的对象,在标记完成之后统一回收所有被标记的对象

不足

  • 效率问题,标记和清除两个过程的效率都不高
  • 空间问题,产生大量不连续的内存碎片

2.2 复制算法

  • 将内存按容量分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另外一块上,然后将已使用过的内存空间一次清理掉
  • 新生代垃圾回收算法,HotSpot虚拟机默认EdenSurvivor的大小比例是8:1,可用参数 -XX:SurvivorRatio设置

2.3 标记-整理算法

标记过程同”标记-清除”算法,整理过程为让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

3. 内存分配回收策略

  • 对象优先在Eden区分配

    • Eden区没有足够空间进行分配,将发起一次Minor GC
    • -Xmn 参数设置新生代大小
  • 大对象直接进入老年代

大对象指需要大量连续内存空间的Java对象,典型的为很长的字符串和大数组

  • 长期存活的对象将进入老年代

可通过参数 -XX:MaxTenuringThreshold设置年龄阈值,默认15

  • 动态对象年龄判定

如果在survivor区相同年龄的所有对象大小超过空间的一半,不必等到MaxTenuringThreshold中要求的年龄,超过或等于该年龄的对象直接进入老年代

  • 空间分配担保

Minor GC 时,Survivor空间内存不足,无法容纳的对象将直接进入老年代,需确保老年代的剩余空间大于晋升对象容量经验值(每次回收晋升到老年代对象容量的平均大小值),否则进行Full GC来让老年代腾出更多空间