上篇

引用计数法

问题:无法处理循环引用,额外空间存储计数器,频繁更新操作

可达性分析算法

从GC Roots集合开始遍历,标记被引用到的对象,加入该集合,这个过程成为标记。未被标记即可回收。
漏报,可达对象未被标记,可能造成JVM崩溃(STW解决)
误报,不可达标记为可达,影响小,失去回收机会

Stop-the-world

JVM收到Stop-the-world请求,等待所有的线程都到达安全点,才允许GC线程进行独占的工作。
停止非GC线程,直到GC完成,造成GC pause(垃圾回收暂停时间)

安全点

安全点的初始目的是找到稳定的执行状态,JVM堆栈不会发生变化,保证垃圾回收器安全执行可达性分析

要保证在可接受性能和内存开销内避免机器码长时间进入安全点,间接减少GC暂停时间
解释执行,JVM可控,有安全点请求时,执行一条字节码便进行一次安全点检测。
即时编译,cpu执行机器码jVM不可控,生成机器码时插入安全点

三种基础回收方式

  • 清除 sweep
    死亡对象内存标记为空闲,记录在空闲列表。新建对象时从空闲列表查询分配。
    问题:碎片化内存,利用率低;分配效率低:需要遍历空闲列表找到合适大小的空闲内存
  • 压缩 compact
    把存活对象聚集到内存起始位置,留下连续空间可解决碎片化问题,但压缩算法性能开销大
  • 复制 copy
  1. 内存分为两等份,分别用from、to指针维护
  2. 只有from区域可分配内存
  3. GC时将from区域存货对象复制到to区域
  4. 交换from、to区域指针内容

解决了碎片化问题,但内存利用率极低

总结

JVM中的垃圾回收器采用可达性分析来探索所有存活的对象。
它从GC Roots集合出发,探索标记引用对象。
为了防止在标记过程中堆栈的状态发生改变,采取安全点机制来实现Stop-the-world,暂停其他非垃圾回收线程。

回收三种方式

  • 会造成内存碎片的清除
  • 性能开销较大的压缩
  • 堆使用效率较低的复制

下篇

大部分对象存活一小段时间,存活下来的小部分会存活很长一段时间
针对对象存活时间特性将堆划分为新生代和老生代,存活时间够长的新生代移动到老生代。
新老代采用不同回收算法

新生代频繁使用耗时短垃圾回收算法回收(Minor GC)
老生代对象在空间不足时,JVM全堆扫描,耗时也不记成本了

堆区划分

  • 新生代
    • Eden
    • 两个大小相同的Survivor
      from、to ,to指向空Suvivor
  • 老生代
    若eden无法存放新对象会直接分配在老生代

根据新对象生产速率采用动态分配策略,调整Eden和Survivor比例
Survivor占比大空间浪费高
老年代和新生代的比例 -XX:NewRatio=value 默认2, 新生代:老生代 1:2
也可直接指定新生代内存大小 -XX:NewSize=value
eden survivor大小按比例设置 -XX:SurvivorRatio=value ,若是8 则单个survivor:eden 1:8, 一个survivor占整个新生代的1/10

TLAB

Thread Local Allocation Buffer 虚拟机参数 -XX:+UseTLAB,默认开启
线程向JVM申请一段连续的内存作为私有的TLAB,后续分配空闲先从TLAB分配,不足在申请新TLAB

Minor GC

Eden空间不足,开启Minor GC收集新生代垃圾,存活的Eden和from复制到to再交换from,to的指针(下次回收时to指向还是空survivor)

晋升老生代

  1. Survivor区对象来回复制15次晋升到老生代(复制次数参数 -XX:+MaxTenuringThreshold)
  2. 单个Survivor占用超50%,也会复制高次数对象到老生代 (比例参数 XX:TargetSurvivorRatio)

卡表

将堆划分为一个个512字节的卡,并且维护卡表来存储每张卡的标识位。标识位代表对应卡是否可能存在指向新生代对象的引用。
将卡表中的脏卡中的对象加入MinorGC的GC Root列表,而不用扫描老生代,扫描完成后将表示为清零。

总结

堆分为新生代和老年代,不同代采用不同的垃圾回收算法。
新生代分为Eden区和两个大小一致的Survivor区,其中一个是空的。
新生代Minor GC中,Eden区和非空Survivor区的存活对象会被复制到空的Survivor区中,当 Survivor区中的存活对象复制次数超过一定数值时,晋升至老年代。、

Minor GC只针对新生代垃圾回收,所以在枚举GC Roots时候,老年代到新生代的引用。为了避免扫描整个老年代,引入了名为卡表的技术,大致地标出可能存在老年代到新生代引用的内存区域。

0%