对象存活性

引用计数法

  • 算法:每个对象有一个引用计数器,每一次对象被引用,计数器+1,引用失效时,计数器-1,计数器为0的对象即未被引用,可以回收
  • 问题:循环依赖问题,循环依赖的对象计数器都不为0

objA.instance=objB; objB.instance=objA;

可达性分析算法(常用)

  • 算法:以一系列称为“GC Roots”对象作为起始点,从这些节点开始往下搜索,形成引用链,当一个对象到GC Roots对象不存在引用链(连通)时,即认为该对象不可达,可以回收
  • GC Roots包括:
    1. 栈帧的本地变量表中引用的对象
    2. 方法区中静态属性引用的对象
    3. 方法区中常量引用的对象
    4. 本地方法中JNI(Native方法)引用的对象

引用

  • 强引用(new Object()):不会被回收
  • 软引用(SoftRefrence): 抛出内存异常之前回收,回收后内存不足才会继续抛出内存移除异常
  • 弱引用(WeakRefrence):GC工作时,无论内存是否足够,都会被回收
  • 虚引用(PhantomRefrence):无任何引用效果,也无法获取实例,唯一作用是在这个对象呗GC回收时获取一个事件通知

finalize方法

  • 是否需要执行finalize?
    1. 判断对象有覆盖finalize方法
    2. JVM尚未调用过finalize() ,即finalize方法只会被调用一次
    3. finalize方法非回收时立即执行,而是回收时先标记是否执行,如需执行,入队列F-Queue,稍后执行

方法区回收(永久代)

  • 废弃常量
  • 无用的类
    1. 所有对象实例都被回收
    2. 加载该类的Classloader被回收
    3. 该类对应的java.land.Class对象没有被任何地方引用

内存回收算法

标记-清除算法(Mark-Sweep)

  • 算法:首先标记所有要回收的对象,在标记完成后统一回收所有被标记的对象
  • 问题:
    • 效率问题:标记和清除的两个规程效率都不高
    • 内存碎片问题:标记和清除会产生大量不连续的内存碎片

复制算法

  • 算法:将可用内存按容量划分大小相等的两块,每次只使用其中一块,当这一块内存用完了,就将存活的对象复制到另一块去,然后将第一块的内存空间一次性清理掉
  • 代价:可用内存缩小为实际内存的一半
  • 解决了效率和复制问题,主流JVM都使用该算法来回收新生代(Eden区+Survivor区)
  • 由于新生代对象存活率低,所以Eden区和Survivor区的大小不是1:1,可以Eden区更大,同时,为了Survivor区的回收效率,可以维护两个Survivor区

标记-整理算法

  • 算法:首先标记所有要回收的对象,然后让所有存活的对象向内存的一端移动,将存活端边界外的对象全部清理
  • 适合老年代的对象回收(对象存活率高)

算法实现

Stopword问题:在进行可达性分析时,需要将jvm中所有线程都停止下来,以使分析过程中,引用关系还在发生变化 - OopMap 1. 类加载时,会对象内什么偏移量上是什么对象类型计算出来 2. JIT编译时会记录栈和寄存器哪些位置是引用 3. 以上信息只在特定位置(安全点)记录,已缩小保存OopMap的内存消耗 4. 程序执行并非在所有地方都可以停顿下来GC,只有在到达安全点时才停止

内存收集器

Serial收集器

  • 单线程
  • 新生代:复制算法,Stop the world
  • 老生代:标记-整理算法,Stop the world
  • 适合Client模式

ParNew收集器

  • Serial收集器的多线程版本
  • 当老生代使用CMS时,新生代只能使用Parnew或Serial

Parallel Scavenge收集器

  • 复制算法,多线程并行
  • 新生代使用
  • 关注吞吐量可控(吞吐量优先) -XX:MacGCPauseMillis:配置GC最大暂停时间 -XX:GCTimeRatio:配置GC时间占比

Serial Old收集器

  • 单线程,使用标记-整理算法
  • 与Parallel Scavenge搭配使用(默认)
  • 作为CMS的后备选择,在并发收集失败(Concurrent Mode Failure)时使用
  • -XX:+UseParallelGC:Parallel Scavenge+Serial Old (Server模式下默认)

Parallel Old收集器

  • Parallel Scavenge的老生代版本
  • 多线程,使用标记-整理算法
  • -XX:+UseParallelOldGC: Parallel Scavenge+Parallel Old

CMS收集器

  • 以获取最短停顿时间为目标的收集器
  • 标记-清除算法
    1. 初始标记,Stop the world,只标识GC Roots能直接关联的对象
    2. 并发标记:GC Roots Tracing标记,无须Stop the world,耗时长
    3. 重新标记:Stop the world,修正并发标记期间程序继续运行而导致的标记变动
    4. 并发清除:并发清理过程中产生的垃圾不能在当次GC时被清理(浮动垃圾)
  • -XX:UseConcMarkSweepGC:ParNew+CMS+Serial Old(备选方案)

  • 问题

    1. CPU消耗
    2. 浮动垃圾问题可能导致来不及释放,需要当老年代未被完全填满时就进行收集(jdk1.5默认68%,jdk1.6默认92%)
    3. 如果CMS回收后内存无法满足应用要求,会引发“Concurrent Mode Failure”,需要触发后备预案(Serial Old)
    4. 标记-清除算法产生的内存碎片问题

G1收集器

  • 特点

    1. 并行与并发
    2. 空间整合
    3. 基于标记-整理算法,从两个region来看基于复制算法
    4. 分区分代收集:将java堆分为多个大小相等的Region,分代是在分区基础上的,新生代、老生代对应多个Region(不需要连续,代之间再是物理隔离)
    5. 可预测的停顿:G1跟踪每个region垃圾回收后的价值大小,维护一个优先列表,每次跟进允许的收集时间,优先回收价值最大的region(Garbage-First名称的来由)
  • 回收步骤

    1. 初始标记(与CMS类似)
    2. 并发标记(与CMS类似)
    3. 最终标记(与CMS类似)
    4. 筛选回收:按成本和回收价值排序,根据用户期望的GC停顿时间来制定和执行回收计划

内存分配和回收策略

  • 对象优先在Eden分配对象
  • 大对象直接进入老年代:应该避免临时大对象的产生,以避免对象在Eden区和两个Servivor区之间发生大量的内存复制,-XX:PretenureSizeThreshold:设置大于这个值得对象直接进入老年代

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

    1. 对象经过一次Minor GC(Eden区回收)后依然存活,并能被Survivor区容纳,则进入Survivor,此时对象的年龄为1,每次Minor GC年龄加1,当超过一定年龄(默认15,-XX:MaxTenuringThreshold控制)则进入老年代
    2. 并不是永远要等到对象年龄达到MaxTenuringThreshold才进入老年代,当Survivor空间中相同年龄对象大小的综合大于Survivor空间的一般,年龄大于或等于改年龄的对象就可以进入老年代