[TOC]
对象的生存与死亡
如何判定一个对象的生与死
引用计数法
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
自适应调节策略开关,使用最大停顿时间和吞吐量参数给虚拟机设立一个优化目标。