什么是垃圾对象
想要了解垃圾回收机制,首先要知道垃圾回收的对象是谁?
jvm中垃圾对象是没有引用指向的一个或一堆对象
如何定位垃圾对象
- 引用计数法
顾名思义,有一个引用指向计数+1,计数为0的则是没有引用指向的对象可以进行回收- 缺点:无法解决循环引用的情况,如下图:
- 缺点:无法解决循环引用的情况,如下图:
- 根可达算法
从GC root开始扫描所有能够到达的对象都是存活对象,那么哪些是根呢?
GC root包含虚拟机栈变量、jni变量、常量、静态变量
分代模型的内存如何划分
分代模型分为老年代和年轻代,老年代和年轻代的比例是1:2,年轻代分为eden区、2个survivor区,eden:survivor0:survior1=8:1:1
垃圾回收算法
标记-清除(mark-sweep)
- 描述:标记可回收对象,然后对标记对象进行清除
- 特点:效率较高,但是由于被标记的可回收对象在内存中肯定不是连续的,就会容易产生碎片,碎片累积过多以后就无法分配大对象,就会提前进行gc
标记-压缩(mark-compact)
- 描述:标记可回收对象,将存活对象进行移动到前面,然后将垃圾对象清除
- 特点:效率低,但是不会产生碎片
复制(copying)
- 描述:将内存一分为二,只使用其中的一部分,回收时将存活对象复制到另一部分中,复制完成后将第一部分全部回收掉
- 特点:效率最高,由于只使用1/2的内存,比较浪费空间,不会产生碎片
三色标记法
- 描述:在三色标记算法中将对将对象分为三种颜色,分别是黑色、灰色、白色,完全扫描完的对象标记为黑色对象,没有扫描到的对象被标记为白色对象,未完全扫描完的对象被标记为灰色对象。
问题1:由于并发标记就会产生一些问题,比如并发标记阶段用户线程操作,对象引用指向发生改变改怎么办?
要解决这个问题,就引申出两个概念:增量更新和原始快照(SATB),使用这两种算法对并发标记过程中发生的变化进行记录。
增量更新:记录并发标记阶段新增的引用,CMS使用增量更新
原始快照:将引用指向变化前的指向记录下来,G1使用SATB
问题2:分代模型中,如果有跨带指向如何处理(老年代指向年轻代)?
老年代指向年轻代,进行YGC的时候难道要扫描整个老年代吗?那这样YGC就和FGC的效果一样了,也就不用区分YGC和FGC了,这里引申出记忆集(Remembered Set)和卡表(Card Table)的概念,这里记忆集相当于一个方案解决的标准,卡表则为具体实现。
卡表是将老年代内存划分成一个一个的卡页,一个卡页可以存放多个对象,如果有跨代指针,则将这个卡页标记为dirty。
问题3:有了卡表后,我们还需要思考在hotspot中是如何将卡页标记为脏的,怎么标记的?
在hotspot中是通过写屏障解决的,这里的写屏障和内存屏障不是一样的,虽然名字一样,但是写屏障类似于AOP切面,分为写前和写后屏障,赋值前为写前屏障、赋值后为写后屏障。如果是高并发情况下,多个线程标记同一个卡页容易出现伪共享问题,为了避免这个问题,可以在标记卡页前进行判断如果卡页已经为dirty则不进行更新。
垃圾回收器
Serial+Serial Old
单线程垃圾回收器,祖师爷级别的垃圾回收器,Serial负责回收年轻代,SerialOld负责回收老年代,回收过程中会发生STW(stop-the-world)
PS+PO
多线程垃圾回收器,jdk8默认垃圾回收器,PS负责回收年轻代,PO负责回收老年代,回收过程中会发生STW(stop-the-world)
CMS
CMS全称是Concurrent Mark Sweep,主要用于老年代的回收,通常和ParNew配合使用,ParNew用于年轻代的回收。看CMS名字也知道,它是一个并发标记清除的垃圾回收器,CMS的出现具有重大意义,CMS出现之前垃圾回收和用户线程是不能同时进行。
CMS主要分为四个阶段:初始标记、并发标记、重新标记、并发清除。
其中初始标记和重新标记阶段会发生STW,但是由于初始标记阶段只标记Roots对象所以停顿时间很短,重新标记阶段是标记并发标记阶段发生变动的对象所以STW时间也不会太长。并发标记是用户线程和gc同时进行,这部分也是最耗费时间的,所以会采用这种方式进行。
- CMS并发故障: 一旦CMS无法在老年代填满之前回收没有完成,或者没有足够空间进行对象分配时就会触发full gc导致所有线程停止运行