《深入理解Java虚拟机》读书笔记(3)——垃圾收集器与内存分配策略

JVM灵魂部分,面试题遇到不知道多少次,重点中的重点


本章思维导图~重点在于GC算法以及各种GC的实现策略

image-20200524205415575

对象死活确认

垃圾收集器在对堆中内存进行回收前,需要确定哪些对象还在“存活”,哪些已经“死亡”

❌引用计数算法

实现:在对象中添加一个引用计数器,每当有一个地方引用它,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器值为零的对象就是不可能再被使用的。

  • 优点:原理简单,判定高效
  • 缺点:有很多额外情况需要考虑,无法解决循环引用的问题。JVM不采用这种方法确认对象是否存活。

✅可达性计数算法

实现:通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果这个对象到GC Roots间没有任何引用链相连,或者说从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

image-20200524210854179

固定可作为GC Roots的对象包括一下子几种:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。

  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。

  • 在本地方法栈中JNI (即通常所说的Native方法)引用的对象。

  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

  • 所有被同步锁(sychronized关键字)持有的对象。

  • 反应JVM内部情况的JMVBean、JVMTI中注册的回调、本地代码缓存等。

除了这些固定的集合外,根据用户所选用的GC以及当前回收的内存区域不同,还可以有其他对象“临时性”加入,共同构成完成的GC Roots集合。

垃圾收集算法

分代收集理论

两条分代假说:

  1. 弱分代假说:绝大多数对象都是朝生夕灭的
  2. 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡

GC的一致设计原则:GC应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。设计者一般至少会把Java堆划分为新生代👶和老年代👴两个区域。新生代朝生夕死,每次回收后存活少量对象,将会逐步晋升到老年代中存放。

第三条经验法则:

  1. 跨代引用假说:跨代引用相对于同代引用来说仅占极少数。
    隐含推论:存在互相引用关系的两个对象,是应该倾向于同生或同死的 。

依据此假说,只用在新生代上建议一个全局的数据结构(被称为“记忆集”),该结构把老年代划分成若干小块,标示出老年代的哪一块内存会存在跨代引用,这样就不用为了少量跨代引用扫面整个老年代

标记-清除算法

  • 思想:算法分成“标记”和“清除”两个阶段。首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。标记的过程就是对象是否属于垃圾的判定过程。
  • 优点:最基础的收集算法
  • 缺点:1.执行效率不稳定。2.内存空间的碎片化问题

img

标记-复制算法

  • 思想:“半区复制”。它将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。
  • 优点:实现简单,运行高效,解决了标记-清除算法面对大量可回收对象时执行效率低的问题
  • 缺点:空间浪费明显,可用内存被缩小至原来的一半
  • 备注1:商用JVM大多优先采用了这种收集算法区回收新生代。
    备注2:更优化的半区复制分代策略,“Appel式回收”。具体做法是把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor空间上。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Survivor空间上,然后清理掉Eden和已用过的那块Survivor空间。Hotspot默认二者大小之比为8:1。逃生门设计:当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保。

img

img

标记-整理算法

  • 思想:其标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都像内存空间一端移动,然后直接清理掉边界以外的内存。

img

经典垃圾收集器

JVM中有很多垃圾收集器的实现,各款经典垃圾收集器之间的关系入下图所示

img

所列一共其中垃圾收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。

Serial收集器

最基础、历史最悠久 的收集器。

特点:是一个单线程工作的收集器,它在进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率。使用“复制”算法

优点:简单高效,所有收集器中额外内存消耗最小

缺点:只适用于线程较少的环境下

应用场景:hotspot 虚拟机客户端模式下的默认新生代收集器

img

Serial/Serial Old 收集器运行示意图
## ParNew收集器

特点:实际上是Serial收集器的多线程并行版本,除了同时使用多条线层进行GC之外,其余行为包括Serial收集器可用的所有控制参数、收集器算法、Stop The World、对象分配规则、回收策略都和Serial完全一致。唯二的能与CMS收集器配合工作的收集器。默认开启的收集线程数与核心数量相同,在核心非常多时,可以用-XX:ParallelGCThreads参数限制线程数。

应用场景:激活CMS收集器后的默认新生代收集器

img

Parallel Scavenge收集器

与吞吐率关系密切,又被称作“吞吐量优先收集器”。

特点标记-复制算法,并行多线程新生代处理器。它的目标是达到一个可控制的吞吐量。一个值得注意的点:垃圾收集的自适应调整策略。

控制吞吐量的两个参数:

  • -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间
  • -XX:GCTimeRatio:设置吞吐量大小

应用场景:对于收集器运行不太了解,优化工作存在困难,使用该收集器配合自适应调整策略(-XX:UseAdaptiveSizePolicy),调优交给虚拟机做。

Serial Old收集器

Parallel Old收集器

CMS收集器

G1收集器

0%