深入理解Java虚拟机读书笔记(二)

深入理解Java虚拟机读书笔记(二)


方法区的回收

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。

  • 满足卸载类的条件

    该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。

    加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如
    OSGi、JSP的重加载等,否则通常是很难达成的。

    该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方

分代收集理论

分代收集理论基于以下两种假说。
弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。

强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消
亡。

正是因为分代收集理论的存在,多种垃圾收集器将Java堆划分不同区域之后就产生了不同的垃圾收集算法,也就是“标记-复制算法”,“标记-清除算法”,“标记-整理算法”。

分代收集的一个缺点:可能存在跨代引用(解决方法就是在新生代上维护一个记忆集)。

标记-清除算法

缺点一:执行效率不稳定

缺点二:内存空间的碎片化问题

标志-复制算法

缺点:浪费空间普通的复制算法会浪费一半的空间

但是Hotspot虚拟机进行了优化,采用“Appel式回收”,划分了Survivor区,又将Survivor划分为FromSurvivor和ToSurvivor两块。每次操作都会将Eden区和一块Survivor进行复制算法,然后将存活对象移到另外一块Survivor。之所以这样做是基于新生代的对象有98%熬不过第一轮收集这个研究。也就不需要为Survivor划分太大的空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,这样可以为Eden区分为更大的空间,减少Minor GC次数。

分配担保策略:当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实 际上大多就是老年代)

标记-整理算法

缺点:移动时,需要停止用户线程。

相比于标记-清理的算法,吞吐量会更高,因为清理算法有内存碎片,在分配对象时会进行更多的内存访问。

卡表:解决跨代引用

并发标记时如何保证标记不出错:

  1. 增量更新(CMS)
  2. 原始快照(G1),原始快照(SATB)

之前已经看过了部分垃圾收集器

G1垃圾收集器分配大对象:

Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个
Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设 定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象, 将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代 的一部分来进行看待

低延迟垃圾收集器

Shenandoah相比于G1的优点:

  1. 在筛选回收阶段运行和用户线程并发
  2. Shenandoah(目前)是默认不使用分代收集的
  3. Shenandoah摒弃了在G1中耗费大量内存和计算资源去维护的记忆集,改用名为“连接矩阵”(Connection Matrix)的全局数据结构来记录跨Region的引用关系。连接矩阵本质上是一个二维矩阵,标记Region之间的引用关系。

Shenandoah使用流程

  • 初始标记,Stop The World,标记GCRoot直接关联的对象
  • 并发标记,遍历对象图,标记可达对象
  • 最终标记,处理剩余原始快照部分
  • 并发清理,这个阶段用于清理那些整个区域内连一个存活对象都没有找到的Region
  • 并发回收,回收垃圾
  • 初始引用更新,并发回收阶段复制对象结束后,还需要把堆中所有指向旧对象的引用修正到复制后的新地址,这个操作称为引用更新
  • 初始引用更新,真正开始进行引用更新操作,这个阶段是与用户线程一起并发的
  • 最终引用更新,修正存在于GC Roots中的引用
  • 并发清理,经过并发回收和引用更新之后,整个回收集中所有的Region已再无存活对象,这些Region都变成Immediate Garbage Regions了,最后再调用一次并发清理过程来回收 这些Region的内存空间,供以后新对象分配使用。

Shenandoah实现并发清理关键:转发指针和读屏障

ZGC

目标:和Shenandoah一样,实现低延迟

ZGC的内存布局:基于Region

ZGC的Region具有动态性——动态创建和销毁,以及动态的区域容量大小

小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。

中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。

大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。

ZGC运行流程

深入理解Java虚拟机读书笔记(二)

并发标记:与G1、Shenandoah一样,并发标记是遍历对象图做可达性分析的阶段,前后也要经过类似于G1、Shenandoah的初始标记、最终标记(尽管ZGC中的名字不叫这些)的 短暂停顿,而且这些停顿阶段所做的事情在目标上也是相类似的。与G1、Shenandoah不同的是,ZGC 的标记是在指针上而不是在对象上进行的,标记阶段会更新染色指针中的Marked 0、Marked 1标志位

并发预备重分配:这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region(并不移动),将这些Region组成重分配集(Relocation Set),ZGC的重分配集只是决定了里面的存活对象会被重新复制到其他的Region中,里面 的Region会被释放

并发重分配:重分配是ZGC执行过程中的核心阶段,这个过程要把**重分配集中的存活对象复制到新的Region上,**并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系

并发重分配:重映射所做的就是修正整个堆中指向重分配集中旧对象的所 有引用。由于旧指针可以自愈,所以并不迫切这个过程。ZGC很巧妙地把并发重映射 阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所 有对象的,这样合并就节省了一次遍历对象图的开销。一旦所有指针都被修正之后,原来记录新旧 对象关系的转发表就可以释放掉了。

ZGC的优缺点

由于ZGC不采用记忆集,不采用分代技术使得给用户线程带来的压力会小很多。但是也因为没有记忆集,在对象分配速率很高的情况下,在这段时间里 面,由于应用的对象分配速率很高,将创造大量的新对象,这些新对象很难进入当次收集的标记范 围,通常就只能全部当作存活对象来看待——尽管其中绝大部分对象都是朝生夕灭的,这就产生了大 量的浮动垃圾。如果这种高速分配持续维持的话,每一次完整的并发收集周期都会很长,回收到的内 存空间持续小于期间并发产生的浮动垃圾所占的空间,堆中剩余可腾挪的空间就越来越小了。