对象的生存与死亡
如何判定一个对象的生与死
引用计数法
Reference Counting给对象中添加一个引用计数器,被引用加1,引用失效减1,计数器为0则对象可以被回收。
缺点:很难解决对象之间互相循环引用的问题。可达性分析法
Reachability Analysis通过一系列
GC Roots的对象作为起点,向下搜索,遍历路径称为引用链。当一个对象到GC Roots不可达时,证明此对象是不可用的。
GC Roots对象包括:- 虚拟机栈中引用的对象
- 方法区中静态常量引用对象
- 方法区中常量引用对象
- 本地方法栈中引用的对象
死亡面前,各种引用不平等
这样的一类对象:当内存空间足够是,保存在内存之中。若内存空间在垃圾回收之后仍不足,则可以抛弃这些对象。应用场景如系统的缓存功能。
强引用
普遍存在的引用,只要引用关系存在,就不会被回收。
软引用
还有用但非必须的对象,系统将要发生内存溢出之前,将会把这些对象列入回收范围之内。
SoftReference类实现。弱引用
描述非必须对象,比软引用更弱一些,被弱引用关联对象只能生存到下一次垃圾收集之前。
WeakReference类实现。虚引用
幽灵引用或幻影引用,不会对对象生存时间造成影响,也无法通过虚引用获取对象实例。
为对象设置虚引用关联唯一目的就是能在这个对象被回收时收到一个系统通知。PhantomReference类实现。
死亡,不过如此 finalize()方法
可达性分析算法中不可达的对象,到被回收之间至少经历两次标记过程:
对象在进行可达性分析之后发现没有与
GC Roots的引用链,会被第一次标记并筛选。
筛选的条件是对象是否有必要去执行finalize()方法。
当对象没有覆盖该方法,或该方法已经被虚拟机调用过,则没必要执行。有必要执行
finalize()方法的对象,被放入F-Queue队列中触发该方法。
这是对象自我拯救的最后一次机会:将自己赋值给某个类变量或成员变量。
稍后 GC 将对F-Queue中对象进行第二次标记,正式宣告对象死亡。
有点像寻梦环游记里的,如果活着的人没人记得你,你就会在亡灵世界里终极死亡。
回收方法区(永久代)
永久代垃圾回收主要是两部分内容:废弃常量与无用的类。
判断常量废弃的条件:
没有任何对象引用常量池中的常量,如字面量、接口、方法、字段的符号引用,垃圾回收时必要时会被回收。
判断类无用的条件:
1. 该类的所有实例已被回收
2. 加载该类的 ClassLoader 已被回收
3. 该类对应的 java.lang.Class对象没有被引用,即无法通过反射访问该类方法。
回收无用的类型,使用 -Xnoclassgc 控制。
在大量使用反射、动态代理、CGLib等字节码框架、动态生成JSP以及OSGI频繁自定义ClassLoader的场景,需要虚拟机具备类卸载功能。
垃圾收集算法
标记-清除算法 Mark-Sweep
原理:
- 标记阶段:对于不可达的对象进行标记。
- 清除阶段:对两次标记的对象进行回收。
缺点:
- 效率问题:标记和清除的效率都不高
- 空间问题:会产生大亮不连续的空间碎片,须分配较大对象时,会因为无法找到足够大的内存而提前触发垃圾收集动作。
复制算法 Copying
原理:
将内存划分为两块,每次使用其中一块,使用完时将其中存活对象复制到另一块,将已经使用的内测空间清理掉。
优缺点:
实现简单,运行高效。
可用内存空间变小,且只适用于年轻代。
内存分配:
新生代分为较大的 Eden 空间和两块较小的 Survivor 空间。每次使用 Eden 和 其中一块 Survivor空间。
HotSpot虚拟机默认 Eden:Survivor = 8:1
当回收时 Survivor 空间不足,需要老年代进行分配担保。
标记整理算法 Mark-Compact
原理:
- 标记同
标记-清除算法中的标记阶段。 - 整理:让所有存活对象向一端移动,然后清理掉边界以外的内存。
优点:
- 解决
复制算法在对象存活率较高时复制操作效率低问题。适用于老年代。 - 不会浪费内存空间。
分代收集算法 Generational Collection
根据对象存活周期不同将内存划分为新生代和老年代。
新生代对象存活少,选用 复制算法。
老年代对象存活率高、没有额外空间分配担保,选用 标记-清除算法 或 标记整理算法
hotspot的算法实现
枚举根节点 GC Roots
原理:
作为GC Roots的节点主要在全局性的引用(常量和静态属性)和执行上下文(栈帧的本地变量表)中。
难点:
- 方法区可能较大,逐个检查引用效率低。
- GC停顿,整个执行系统中引用关系不断变化会影响分析结果准确性。
Stop The Word
解决:
- 使用一组称为
OopMap的数据结构,直接得知执行上下文和全局引用的位置。 - 使用
OopMap可快速完成GC Roots枚举,但前提是枚举时引用关系不会变化。这个使用安全点Safepoint解决。
安全点 Safepoint
问题:
GC Roots枚举时可能引用关系变化,或者说,导致 OopMap 内容变化的指令非常多,若为每条指令生成 OopMap,将会需要大量额外空间。
解决:
只在特定位置记录 OopMap 信息,这些位置即安全点。
程序执行到安全点才可以GC。
安全点的选定:
是以程序是否具有让程序长时间执行的特征为标准选定的,特征是指令序列复用,如方法调用、循环跳转、异常跳转等。
意思是在你经常路过的地方堵着你。
如何在GC发生时让所有线程都泡到安全点上停顿下来?
抢先式中断
先把所有线程全部中断,如果有线程中断的地方不在安全点上,就恢复线程让它跑到安全点上。
主动式中断
当GC需要中断线程时,见设置一个标识,个线程执行时主动轮询该标识,发现为真时就自己中断挂起。轮询标志的地方与安全点重合。
安全区域 SafeRegion
Safepoint 的问题:
若程序不执行如处于 Sleep 或 Blocked 状态的线程,无法响应JVM的中断请求。
解决:
安全区域指在一端代码片段中,引用关系不会发生变化。可以看作扩展的 Safepoint。
线程执行到 SafeRegion 时标识自己已经进入到 SafeRegion ,若这段时间JVM发起GC,就不用管这样状态的线程了。
在线程离开 SafeRegion 时检查系统是否已经完成根节点枚举,如果没有完成,必须等待收到可以离开 SafeRegion 的信号为止。
垃圾收集器
Serial 串行收集器
原理:
进行垃圾收集工作时,必须暂停其他所有工作线程,即GC线程需要和用户线程串行。
优点:
简单高效,Clent 模式下默认新生代收集器。
缺点:
Stop The Word 停顿时间长。
ParNew(颇牛) 多线程串行收集器
原理:
Serial收集器的多线程版本,GC线程并发执行,暂停所有用户线程。
特点:
ParNew作为新生代收集器 可以和 CMS老年代收集器搭配使用。
是使用 -XX:+UserConcMarkSweepGC选项后的默认新生代收集器,也可以使用 -XX:+UserParNewGC 选项使用它。
Parallel Scavenge 并行的多线程收集器
Parallel Scavenge 收集器目的是达到一个可控制的吞吐量,吞吐量优先收集器。
吞吐量就是CPU运行用户代码时间与CPU总消耗时间的比值。
吞吐量 = 用户代码时间 / ( 用户代码时间 + 垃圾收集时间 )
CMS等收集器关注点则在缩短停顿时间。
追求短停顿时间和追求可控吞吐量的区别?
GC停顿时间短是牺牲吞吐量和新生代空间来换取的。
停顿时间短,则可能GC停顿频繁,整体上用户代码时间缩短,吞吐量不高。
停顿时间短适合用户交互程序;提高吞吐量则高效率利用CPU时间,适合后台计算任务。
Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量。
-XX:MaxGCPauseMillis最大垃圾收集停顿时间,大于0。-XX:GCTimeRatio直接设置吞吐量大小,大于0,小于100,默认99,允许最大1%的垃圾收集时间。-XX:+UserAdaptiveSizePoliy自适应调节策略开关,使用最大停顿时间和吞吐量参数给虚拟机设立一个优化目标。
Serial Old 收集器
Serial 收集器的老年代版本,单线程收集器,使用 标记-整理 算法,Client模式下使用。
jdk1.5之前与 Parallel Scavenge 收集器搭配使用
作为CMS收集器后备预案
Parallel Old 收集器
Parallel Scavenge 收集器老年代版本,使用多线程和 标记-整理 算法
吞吐量优先 收集器组合 Parallel Scavenge + Parallel Old 。
CMS 收集器
一种以获取最短回收停顿时间为目标的收集器,基于 标记-清除 算法实现。
运作过程
初始标记
需要
Stop The World, 仅仅标记GC Roots直接关联到的对象。并发标记
进行
GC Roots Tracing的过程,标记引用链,与用户线程同时进行重新标记
需要
Stop The World, 修正并发标记期间因用户线程变动的标记。并发清除
对标记的内存回收,与用户线程同时进行。
缺点
CMS收集器堆CPU资源敏感。- 无法处理浮动垃圾,可能
Concurrent Mode Failure失败,临时启用Serial Old重新收集老年代。 - 产生大量的空间碎片,使用
-XX:+UseCMSCompactAtFullCollection开关参数,FULLGC时开启内存碎片整理。
G1 收集器
Garbage-First 收集器。
特点
并行与并发
利用多CPU和多核的优势。
分带收集
仍保留年轻代和老年代概念,不需要和其他垃圾收集器配合。
空间整合
使用
标记整理算法,不会浪费一个Survivor空间,也不需要进行空间整理。可预测的停顿
根据各区域的垃圾回收价值维护优先列表,针对设置的最低停顿时间计划性回收。
运作过程
初始标记
需要
Stop The World, 仅仅标记GC Roots直接关联到的对象。并发标记
进行
GC Roots Tracing的过程,标记引用链,与用户线程同时进行最终标记
需要
Stop The World, 修正并发标记期间因用户线程变动的标记,将改动记录到Remember Set中。筛选回收
根据用户设置的停顿时间和各
Region的回收价值制定回收计划。
gc日志
垃圾收集器参数
内存分配与回收策略
- 优先在Eden区分配,若空间不足,虚拟机发起一次
Minor GC - 在
Serial和ParNew收集器中可以设置-XX:PretenureSize参数,使大对象直接进入老年代。 - 长期存活对象进入老年代,设置
-XX:MaxTenuringThresold设置晋升到老年代的年龄。 - 空间分配担保,年轻代进行
Minor GC时,Survivor空间不足存放存活的对象,则进入老年代。