垃圾收集器与内存分配策略
1,对象存活性检测
1,引用计数器算法
每增加引用+1,每引用失效-1,为0则未被使用
优点:简单,高效
缺点:不能解决对象循环依赖问题
Hotspot未采用引用计数器算法
2,可达性分析算法:GC Roots(Java中采用)
GC Roots对象作为起始点,向下搜索引用链,不可及的对象则视为不可用
GC Roots对象:
1,虚拟机栈(栈帧本地变量表)中引用的对象
2,方法区类静态属性引用的对象
3,方法区常量引用的对象
4,本地方法栈JNI引用的对象
3,引用分类
1,强引用:引用存在虚拟机永不回收
2,软引用:内存溢出之前列入回收队列,回收后还是内存不足则oom,SoftReference
3,弱引用:生存到下一次GC之前,WeakReference
4,虚引用:毫无影响,只是在被回收的时候收到系统通知,PhantomReference
强引用 > 软引用>弱引用>虚引用
4,回收两次标记过程:
1st:GC Roots引用链不可达&&对象finalize方法有必要执行,放入F-Queue队列,虚拟机开启一个Finalizer线程执行队列中对象的finalize方法
2nd:对F-Queue队列中的对象第二次标记,(如果在对象的finalize()方法中将自己关联到GC Roots的引用链上,则不会回收),未逃离回收集合则被回收
5,回收方法区(在永久代)
主要是常量和类
常量:没有引用则回收,包括类/接口/方法/字段的符号引用
类:同时满足3个条件
1,其所有实例都已经被回收
2,加载该类的ClassLoader已经被回收
3,该类的Class对象没有被引用,且无法通过反射访问该Class对象
2,GC 回收算法
1,标记-清除算法(最基础的收集算法)
分为标记(4,回收两次标记过程)和清除两个阶段
不足:
1,效率问题:标记和清除过程效率都不高
2,空间问题:标记清除产生大量不连续的内存碎片,无法分配较大的对象(触发另一次GC)
2,复制算法(解决效率问题)
1,内存分为大小相等的两块,使用其中一块儿分配内存,回收时将仍然存活的对象复制到另外一块儿内存,一次清理当前内存块(没有内存碎片),分配内存时顺序分配
优点:简单高效
缺点:内存缩小一半
2,jdk中,将内存分为一大(Eden空间)两小(Survivor空间),每次使用Eden+其中一块儿Survivor,回收时将Eden+和使用的那块儿Survivor中存活的对象复制到另外一个Survivor空间,清理掉原来的Eden+Survivor,默认Eden:Survivor=8:1
3,老年代的分配担保
3,标记-整理算法(老年代的回收)
与标记-清除的区别:标记完成后不清理,而是将存活的对象移动到内存的另一端,清理掉边界外的内存(对象)
4,分代收集算法
根据对象生命周期将内存划分为几块,一般划分为新生代和老年代,新生代回收用复制算法,老年代回收用标记-清理,标记-整理算法
堆区:
新生代
Eden,Survivor1,Survivor2
老年代
3,
1,枚举根节点
一致性,STW,OopMap
2,safepoint
让程序长时间执行的特征作为条件
3,safeRegion
代码片段中,引用关系不会发生变化
3,垃圾回收器
新生代:Serial,ParNew,Parallel Scavenage, G1
老年代:CMS,Serial Old(MSC),Parallel Old,G1
新老搭配关系:
Serial---->CMS,Serial Old
ParNew---->CMS,Serial Old(MSC)
ParallelScanvenge----->Serial Old,Parallel Old
//------------------------------------------------
G1---->G1
CMS---->Serial,ParNew,Serial Old(老)
Serial Old--->CMS(老),Serial,ParNew,Parallel Scanvenge
Parallel Old ---->Parallel Scanvenge
G1-----> G1
1,Serial收集器
最基本的,STW,单线程
Client下新生代默认的收集器,因为新生代比较小,简单高效
复制算法
2,ParNew收集器
Serial的多线程版本
采用复制算法
Server模式下首选的新生代收集器,新生代只有ParNew和Serial能配合CMS(老年代)工作
单核CPU效果不明显
3,Parallel Scavenge收集器
复制算法,多线程
吞吐量优先,吞吐量=运行用户代码时间/(运行用户代码时间+GC垃圾收集时间),高吞吐量适合后台运算交互不多的任务
GC自适应调节策略 -XX:UseGCAdaptiveSizePolicy
4,Serial Old收集器
标记-整理算法,单线程
Client模式下使用,CMS收集器的后备预案
图见Serial收集器
5,Parallel Old收集器
标记-整理算法,多线程
注重吞吐量和CPU资源敏感的场景,Parallel Scanvenge + Parallel Old
6,CMS收集器
目标:最短回收停顿时间,注重相应性能
标记-清除算法
4个步骤:
初始标记:STW,标记GC Roots直接关联的对象,很快
并发标记:GC Roots Tracing,耗时长,可以用用户线程并发
重新标记:修正并发期间用户线程产生的标记变动,时间短于并发标记,长于初始标记
并发清除:可以用用户线程并发
优点:并发收集,低停顿
缺点:
对CPU资源比较敏感,CPU数量少的时候,占用较多CPU资源,改进算法i-CMS(然并卵)
CMS无法处理浮动垃圾,并发清理时用户线程也在生成垃圾,在Concurrent Mode Failed时,后备预案,临时启用Serial Old来收集老年代,时间变长
大量空间碎片,标记-清除算法的通病,内存碎片的合并整理
7,G1收集器
目标是替换调CMS
整体来看是标记-整理算法
特点:
并行与并发:多CPU缩短STW时间,用户程序仍在并发
分代收集:单独的算法处理新创建对象和老对象
空间整合:Region局部采用复制算法,不会有空间碎片
可预测停顿:可预测停顿时间模型,降低停顿时间
内存布局的变化:没有新生代和老年代的区别,所有都划分为等大小的Region区,新老不再物理隔离
避免全区域垃圾回收,根据Region判断回收价值,回收价值最大的Region(G1),有限时间获取更高的回收效率
可达性判断问题更突出,采用Region的RemeberSet来处理
回收过程:
初始标记
并发标记
最终标记:并发标记将对象变化记录在线程的Remebered Set Log,在这个阶段将RemeberSetLog中的变化记录到RememberSet中
筛选回收
4,内存分配和回收策略
在堆(Heap)上分配(JIT标量类型间接在栈上分配),主要是在Eden区上分配,TLAB,少数直接在老年代分配
对象优先在Eden区分配(分配)
Eden区空间不足,触发Minor GC
新生代(Eden区+一个Survivor区)
新生代GC:Minor GC
老年代GC:Full GC/Major GC,Full GC经常伴随至少一次Minor GC
大对象直接进入老年代(分配)
-XX:PretenureSizeThreshold,大于这个参数值的对象直接分配在老年代(只对ParNew和Serial收集器有效)
长期存活的对象将进入老年代
对象年龄计数器,每熬过一次Minor GC,则年龄计数器+1,达到阈值(-XX:MaxTenuringThreshold,默认15)则对象晋升到老年代
动态对象年龄判断
Survivor中相同年龄X的对象大小总和 > 1/2 Survivor空间,则对象年龄>=X的对象直接进入老年代
空间分配担保