【JVM深入理解系列5】精通垃圾回收

分代复制算法指针移动原理

数据发生了移动,为什么依然能够访问到。

JVM四大层面

java语法
java字节码
openjdk原理

openJdk源码

强软弱虚引用

前提:
未发生GC 所有的对象都是白色。
发生GC时新建的对象都是hi黑色。

  • 标记阶段做的事情
  1. 初始化标记
    只标记 GC Roots直接关联的对象

    对象直接充当GC Root

    只标记A,E 标记成灰色

    会STW,耗时很少。

  2. 并发标记阶段 (三色标记)
    不需要STW
    耗时很久
    将GC Roots直接关联的对象所有引用链全部跑一遍
    一层一层遍历,正常所有被标记的都会变成黑色。

  • 三色标记

GC:
串行
用户线程会STW
一个Thread执行gc
并行
用户线程STW,多个GC线程运行
并发
不需要STW,用户线程,GC线程并发。(需要三色标记)

  1. 最终标记

并发标记会带来三个问题

  1. 多标
    A->B-C A是黑色,B是灰色,C是白色,此时 A不引用B了。B还是灰色,没有变成黑色。 不会被回收。

  2. 少标
    标记程序在运行的过程中,用户线程依然创建对象。

    由于是新创建的所以是黑色的。
    创建的对象会躲过这次GC,但是下次GC有可能会被回收。

  3. 漏标
    GC标记程序运行过程中,引用链发生改变。

    B(灰)对D引用取消了。
    A(黑)引用了D.
    这个会导致D被回收,执行就会报错。

解决方法:增量更新,原始快照。

多标和少标在下轮GC会被回收掉。

如何解决漏标:

  1. 当黑色对象直接引用了一个白色对象后,我们就将这个黑色对象记录下来(加入 oopMap),在扫描完成后,重新对这个黑色对象扫描,这个就是增量更新(Incremental Update)

  2. 当删除了灰色对象到白色对象的直接或间接引用后,就将这个灰色对象记录下来,再以此灰色对象为根,重新扫描一次。这个就是原始快照(Snapshot At TheBeginning,SATB)

  • 读写屏障

原方法write

在执行前加上 pre_write,在执行之后执行 post_write。

核心垃圾回收期

现在的垃圾回收期发展趋势:模块化,支持并发。
默认 Parallel Scvenge,Parallel Old

CMS

分代
写屏障 + 增量更新

G1

基于region 默认2M,4M,8M,16,32M

2048个Regin
每个 region都可以是e,f,to老年代,但是一个region只能有一个角色。

控制耗时

Garbege First == G1

为什么耗内存?
20% - 30% 内存存一些数据结构。
优先队列维护一个 Region优先级数据。每次回收10ms。能回收几个region算几个。

空间换时间。

  • 记忆集与卡表

首先跨代引用的问题?

存在跨代引用时,在进行YGC时,如果young generation的Y对象被old generation中O对象引用,那么称O对象存在跨代引用,而且Y对象应该在本次垃圾回收中存活下来,所以old generation的对象在YGC时也是Strong root的一部分,如果每次YGC都去扫描old generation中所有对象的话,肯定会非常耗时。

解决跨代引用
老年代引用新生代。
YGC时候对象被回收了。老年代会报错
新生代引用老年代。
YGC时候,也要扫描整个老年代,非常耗时。

记忆集:GenRemSet
卡表:CardTable

记忆集是一个理论,卡表是实现。

  • 卡表的实现原理
    每个Region 2M.
    卡表
    卡页

    卡表中有2048个卡页

    一个卡页对应一个Region
    一个卡页是512B

    卡页中的一个B标识Region中的4KB(2M/512)
    00000001
    标记这个region是否有对年轻代的引用。

如果没有记忆集:
需要遍历所有的老年代的region,遍历所有region中的所有对象。

说白了,记忆标记了某个region是否有引用年亲代引用,然后只遍历有年轻代引用的region。减少了遍历的region数量。

  • savePoint

安全点:

150 + 90 + 275 - 40 =