[深入理解JVM虚拟机]第3章-垃圾收集器、内存分配策略

时间:2021-10-28 23:51:57

垃圾收集器

判断对象是否需存活

  • 回收堆
    • 判断对象是否存活:
      • 方法一:引用计数法。对象被引用一次就+1,当为0时回收对象。缺点:无法解决循环引用问题。
      • 方法二:可达性分析算法。记录当前对象是否有和GC Roots中对象的引用链。(其中,可以作为GCRoots对象的有:虚拟机栈中引用的对象、方法去中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中引用的对象。)
        • 不可达对象并不是一定被垃圾收集的,当这个对象有必要执行finalize()并finalize里自己和某个对象建立关联,即可在第二次标记时被移出“即将回收”的集合。但强烈建议不在finalize()里来拯救对象,使用try-finally等其他方式或许更好。
    • 引用分为:强引用(new出来的)、软引用(SoftReference类实现)、弱引用(WeakReference类实现)、虚引用(PhantomReference类实现)。是为了描述一类对象:内存空间还够的时候能保存在内存,不够就可以抛弃这些对象。
  • 回收方法区
    • 主要是回收方法区中的废弃常量无用的类
      • 废弃常量:假如在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量,就说明常量 "abc" 就是废弃常量
      • 无用的类必须满足三点:Java堆中不存在该类实例;加载该类的ClassLoader已经被回收;该类对应的java.lang.Class对象没有在其他地方被引用。
      • 无用的类不一定要被回收,可以通过参数与进行控制。

垃圾收集算法(内存回收方法论)

  • 分代收集算法:把堆对象分为新生代(存活率低的对象)和老年代(存活率高的对象)。
    • 老年代:
      • 方法一:标记-清除算法:把要回收的对象标记一下,然后清除。缺点:标记和清除两个操作效率低、会造成空间碎片可能导致二次回收。
      • 方法二:标记- 整理算法:把要回收的对象标记一下,然后留下的对象连续排。
    • 新生代:复制算法
      • 把堆内存分为两部分,只使用其中的一部分装对象,当装不进去后,把留存对象拷贝到另一部分,连续放置。
      • 两部分比例可以超过1:1比如9:1,但要使用内存担保,用老年代的内存空间担保,即1放不下时候放老年代去。所以只适合新生代使用。

HotSpot的算法实现(HotSpot如何发起内存回收)

  • 关于枚举根节点:
    • HotSpot中,使用OopMap数据结构使虚拟机直接指导哪些地方存着对象引用,这样GC扫描时可以直接得知这些信息。
    • 具体地,类加载完成后,HotSpot就把对象内什么偏移量是什么数据类型算出来。JIT编译过程中,也会记录下栈和寄存器中哪些位置是引用。
  • 安全点:
    • 因为对象引用是变化的,所以OopMap是变化的,但不可能每一时刻都记录对应的OopMap,故只有在安全点才记录了OopMap,GC只有在安全点才回发生,使线程暂停。
    • 如何让所有线程跑到安全点才停下来?主流采用主动式中断,在安全点设置标志,各个线程主动去轮询到没到标志,到了的话就停下。
  • 安全区域:
    • 当程序不执行的时候,即没有分配CPU时间的时候,无法响应JVM的中断请求走到安全点挂起,这就需要安全区域。
    • 安全区域是指在一段代码片段中,引用关系不发生变化。
    • 当线程执行到安全区域,就标识进入安全区域,此时JVM发起GC就不用管这个线程了,当线程要离开安全区域时,检查是否已完成了GC,完成即可离开安全区域。

垃圾收集器(内存回收的具体实现、特点)

重点1:CMS(Concurrent Mark Sweep)收集器:老年代收集器

  • 并发的收集器
  • 以减少用户线程的停顿,加快响应速度为目标
  • 基于标记-清除算法
  • 步骤
    • 1 初始标记:标记和GC Roots直接关联的对象(stop worlds)。
    • 2 并发标记:垃圾回收线程和用户线程并发,进行可达性分析,标记所有和GC Roots相连的节点。
    • 3 重新标记:由于上一步可能会有标记更改,对这些更改进行重新标记(stop worlds)。
    • 4 标记清除:垃圾回收线程和用户线程并发,进行标记请清除
  • 缺点
    • 1 对CPU资源敏感。因为占用了一部分线程,所以虽然减少停顿,但会导致应用程序变慢,尤其是CPU资源少的时候。
    • 2 无法处理浮动垃圾。由于4标记清除是并发进行的,这一过程用户线程新产生的垃圾本次无法处理,并且要预留一部分内存空间给此时产生的垃圾,如果预留的不够,就会造成本次垃圾收集失败,导致再次GC。
    • 3 由于使用标记-清除算法,所以会产生内存碎片。

重点2:G1 (Garbage-First) 收集器:自己可以负责全部的GC收集

  • G1是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。
  • 特点
    • 可以不与其他垃圾收集器配合独自管理GC,但是在内部仍保留分代概念。
    • 整体基于标记-整理算法,局部基于复制算法。
    • 可预测的停顿时间:建立可预测的停顿时间模型,能让使用者明确指定stop worlds在一个长度为 M 毫秒的时间片段内。 内部实现:使用 Region 划分内存空间,G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region。保证了 GF 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
    • 并行与并发:可利用多颗cpu、多核优势,减少stop worlds 时间。并且支持并发。
  • 步骤
    • 初始标记
    • 并发标记
    • 最终标记
    • 筛选回收

Serial收集器(新生代、单线程、复制算法、Client模式)、ParNew收集器(新生代、多线程、复制算法、Server模式)、Serial Old收集器(老年代、单线程、标记整理算法、Client模式)、CMS

这其中的新生代可以与老年代搭配使用。

Parallel Scavenge收集器(新生代、多线程、重吞吐、有自适应调节策略、复制算法) 、Parallel Old收集器(老年代、多线程、标记整理算法)

两者可搭配使用。

重吞吐是指应用程序总体完成时间短,但停顿可能大一些,适合后台、用户交互少的应用。

理解GC日志

GC日志包含:发生时间、停顿类型(GC/Full GC)、DefNew等表示GC发生的区域时新生代/老年代/永久代、GC前后内存总容量变化、堆内存容量变化。

Minor GC 与 Major GC

  • 新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
  • 老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。

内存分配

todo

参考连接

https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/JVM垃圾回收.md#46-cms-收集器