本章思维导图~重点在于GC算法以及各种GC的实现策略
对象死活确认
垃圾收集器在对堆中内存进行回收前,需要确定哪些对象还在“存活”,哪些已经“死亡”
❌引用计数算法
实现:在对象中添加一个引用计数器,每当有一个地方引用它,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器值为零的对象就是不可能再被使用的。
- 优点:原理简单,判定高效
- 缺点:有很多额外情况需要考虑,无法解决循环引用的问题。JVM不采用这种方法确认对象是否存活。
✅可达性计数算法
实现:通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果这个对象到GC Roots间没有任何引用链相连,或者说从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
固定可作为GC Roots的对象包括一下子几种:
在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
在本地方法栈中JNI (即通常所说的Native方法)引用的对象。
Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
所有被同步锁(sychronized关键字)持有的对象。
反应JVM内部情况的JMVBean、JVMTI中注册的回调、本地代码缓存等。
除了这些固定的集合外,根据用户所选用的GC以及当前回收的内存区域不同,还可以有其他对象“临时性”加入,共同构成完成的GC Roots集合。
垃圾收集算法
分代收集理论
两条分代假说:
- 弱分代假说:绝大多数对象都是朝生夕灭的
- 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
GC的一致设计原则:GC应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。设计者一般至少会把Java堆划分为新生代👶和老年代👴两个区域。新生代朝生夕死,每次回收后存活少量对象,将会逐步晋升到老年代中存放。
第三条经验法则:
- 跨代引用假说:跨代引用相对于同代引用来说仅占极少数。
隐含推论:存在互相引用关系的两个对象,是应该倾向于同生或同死的 。
依据此假说,只用在新生代上建议一个全局的数据结构(被称为“记忆集”),该结构把老年代划分成若干小块,标示出老年代的哪一块内存会存在跨代引用,这样就不用为了少量跨代引用扫面整个老年代
标记-清除算法
- 思想:算法分成“标记”和“清除”两个阶段。首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。标记的过程就是对象是否属于垃圾的判定过程。
- 优点:最基础的收集算法
- 缺点:1.执行效率不稳定。2.内存空间的碎片化问题
标记-复制算法
- 思想:“半区复制”。它将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。
- 优点:实现简单,运行高效,解决了标记-清除算法面对大量可回收对象时执行效率低的问题
- 缺点:空间浪费明显,可用内存被缩小至原来的一半
- 备注1:商用JVM大多优先采用了这种收集算法区回收新生代。
备注2:更优化的半区复制分代策略,“Appel式回收”。具体做法是把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor空间上。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Survivor空间上,然后清理掉Eden和已用过的那块Survivor空间。Hotspot默认二者大小之比为8:1。逃生门设计:当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保。
标记-整理算法
- 思想:其标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都像内存空间一端移动,然后直接清理掉边界以外的内存。
经典垃圾收集器
JVM中有很多垃圾收集器的实现,各款经典垃圾收集器之间的关系入下图所示
所列一共其中垃圾收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。
Serial收集器
最基础、历史最悠久 的收集器。
特点:是一个单线程工作的收集器,它在进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率。使用“复制”算法
优点:简单高效,所有收集器中额外内存消耗最小
缺点:只适用于线程较少的环境下
应用场景:hotspot 虚拟机客户端模式下的默认新生代收集器
特点:实际上是Serial收集器的多线程并行版本,除了同时使用多条线层进行GC之外,其余行为包括Serial收集器可用的所有控制参数、收集器算法、Stop The World、对象分配规则、回收策略都和Serial完全一致。唯二的能与CMS收集器配合工作的收集器。默认开启的收集线程数与核心数量相同,在核心非常多时,可以用-XX:ParallelGCThreads
参数限制线程数。
应用场景:激活CMS收集器后的默认新生代收集器
Parallel Scavenge收集器
与吞吐率关系密切,又被称作“吞吐量优先收集器”。
特点:标记-复制算法,并行多线程新生代处理器。它的目标是达到一个可控制的吞吐量。一个值得注意的点:垃圾收集的自适应调整策略。
控制吞吐量的两个参数:
-XX:MaxGCPauseMillis
:控制最大垃圾收集停顿时间-XX:GCTimeRatio
:设置吞吐量大小
应用场景:对于收集器运行不太了解,优化工作存在困难,使用该收集器配合自适应调整策略(-XX:UseAdaptiveSizePolicy
),调优交给虚拟机做。