笔记——gc回收
1.引用计数法(对象中添加一个引用计数器)
2.可达性分析法
GC ROOTS
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
**不可达的对象也要至少经历两次标记,对象进行可达性分析后与GC roots的引用链无关,就会标记一次,然后判断是否有必要执行finalize()方法进行第二次标记判断。**假如对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,那么这两种就被视为没有必要执行。
对象有必要执行finalize()方法就被放置在一个F-Queue的队列中,并稍后去执行,这里的执行是会触发方法开始运行,但不一定承诺会等待它结束
finalize()方法只会被系统调用一次
垃圾收集算法
1、标记-清除算法(Mark-Sweep)
“标记-清除”算法是最基础的算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。它主要由两个缺点:一个是效率问题,标记和清除过程的效率都不高;另一个是碎片化
2、标记-复制算法(Copying)(针对新生代)
为了解决标记清除算法的效率问题,出现了复制算法,它将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。优点是每次都是对其中的一块进行内存回收,内存分配时就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。缺点是将内存缩小为原来的一半,代价太高了一点。
Appel式回收将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保。即如果另外一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。
3、标记-整理算法(Mark-Compact)(针对老年代)
根据老年代的特点提出了“标记-整理”算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
标记-整理的步骤:
- 标记阶段
- 整理阶段:移动存活对象,同时更新存活对象中所有指向被移动对象的指针
4、分代收集算法(Generational Collection)
一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
安全点
-
方法返回之前
-
调用某个方法之后
-
抛出异常的位置
-
循环的末尾
记忆集与卡表
记录集:记录从非收集区到收集区的指针集合(解决跨代引用)
**卡表:**实现记忆集最常用的一种方式
卡表是使用一个字节数组实现
一个卡页中可包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成1,表示该元素变脏,否则为0.
GC时,只要筛选卡表中变脏的元素加入GCRoots。
Stop The World
暂停所有的工作线程,直到它收集完成
写屏障
使用写屏障维护卡表状态
垃圾收集器
Serial (新生代)
1.单线程(收集期间需要暂停其他所有线程)
2.客户端模式下的默认收集器
优点:
1.简单高效(与其他收集器的单线程相比)
2.内存资源受限时,额外内存消耗最小
3.因为没有线程交互的开销,因此可获得最高的单线程效率
ParNew(新生代)
-
多线程并行收集(用户进程需等待)
-
目前唯一一个能与CMS收集器配合工作
使用
-XX:+UseConcMarkSweepGC
参数,使用ParNew+CMS+Serial Old收集器组合,此时ParNew是新生代的默认收集器 -
默认开启的收集线程数与处理器核心数相同。
可以使用
-XX:ParallelGCThreads
参数限制线程数
Parallel Scanvenge(新生代)
1.基于标记-复制
2.并行收集
3.关注吞吐量 用户代码时间/(用户代码时间+垃圾收集时间)
适合在后台运算而不需要太多交互的任务
Parallel Old(老年代)
- 多线程并发收集
- 基于标记整理
- 注重吞吐量(Parallel Scanvenge + Parallel Old)
Serial Old(老年代)
CMS(老年代)
关注停顿时间—> B/S等服务端模式中给用户给拱良好的交互体验
运作过程
**1.初始标记 (Initial Mark)**标记与GCRoots直接关联的对象,速度快
**2.并发标记(Concurrent Mark)**从GCRoots直接关联的对象开始遍历整个对象图,不暂停用户线程,可与GC线程并发运行,耗时长
**3.重新标记(Remark)**修正并发标记期间因用户线程导致标记产生变动的对象
**4.并发清除(Concurrent Sweep)**删除掉被标记死亡的对象,可与用户程序并发运行
缺点
- 占用了一部分线程
- 无法处理浮动垃圾
- 空间碎片过多
Garbage First(G1)
基于Region的内存布局
Region还有一类Humongous区域,专门存储大对象(只要超过一个region容量一半的对象即可判定为大对象)
运行过程
-
初始标记
-
并发标记
-
最终标记
-
筛选回收
- 更新region的统计数据
- 对所有region按照回收价值和成本排序
- 根据用户设定的停顿时间制定回收计划
- 自由选择多个region作为回收集,将决定回收的region的存活对象复制到另一个空的region,再清理整个旧region
- 需暂停用户进程,可多条收集器并行执行。
更新->排序->制定回收计划->选择回收集->复制清理(并行、STW)
除了并发标记,都需要停止用户进程。
优点:
-
延迟可控情况下获得尽可能高的吞吐量。
-
用户可指定期望(要符合实际)的停顿时间
默认为200ms,不能太低,太低会造成每次选择的回收集只占堆的很小一部分,回收速度跟不上分配速度,导致Full GC
-
分区(region)管理堆内存
-
按回收效益确定回收集
-
整体基于标记-整理,局部基于标记-复制,不会产生碎片
缺点(与CMS比)
-
内存占用
因为每个region都需要维护一个卡表解决跨代引用问题,记忆集可能占整堆的20%以上。CMS则只有一份卡表。
-
额外执行负载高
和CMS一样都使用写屏障维护卡表。
除了和CMS一样使用写后屏障外(直接使用同步),G1还需要写前屏障实现原始快照算法,产生额外负担。(使用消息队列)