美文网首页小卜java
JAVA基础之内存回收

JAVA基础之内存回收

作者: 汤太咸啊 | 来源:发表于2021-11-21 21:12 被阅读0次

    今天整理内存回收,面试很重要哦。

    1. 引用计数法:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。不是主流以及jvm的内存回收机制。
    2. 可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
    3. Java对引用的概念进行了扩充,将引用分为强引用(Strongly Re-ference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。
    4. 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
    5. 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
    6. 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
    7. 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
    8. 标记-清除算法:分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
    9. 标记-复制算法常被简称为复制算法。为了解决标记-清除算法面对大量可回收对象时执行效率低的问题。将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
    10. “标记-整理”(Mark-Compact)算法,其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。
    11. 对象存活判定算法以及垃圾回收算法,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的。OopMap的数据结构用来存储对象的引用。
    12. 安全点:实际上HotSpot也的确没有为每条指令都生成OopMap,前面已经提到,只是在“特定的位置”记录了这些信息,这些位置被称为安全点(Safepoint)。抢先式中断(Preemptive Suspension)和主动式中断(VoluntarySuspension),抢先式中断在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。几乎没有虚拟机实现采用抢先式中断的。而主动式中断是当垃圾收集需要中断线程的时候,仅仅简单地设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。轮询标志的地方和安全点是重合的,另外还要加上所有创建对象和其他需要在Java堆上分配内存的地方,这是为了检查是否即将要发生垃圾收集,避免没有足够内存分配新对象。
    13. 安全区域是为了解决处于sleep等状态的线程无法响应中断请求的情况。这块区域指的是某一代码片段中,在整个区域内都是安全的。用户线程进入安全区域,垃圾收集自动忽略这部分安全区域的线程,但是当要离开安全区域时,检查虚拟机是否完成根节点枚举,如果完成则离开该区域,如果未完成则一直等待。
    14. 记忆集与卡表,记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。不同的记录精度,字长景度:每个记录精确到一个机器字长,该字包含跨代指针;对象精度:每个记录精确到一个对象,该对象包含跨代指针;卡精度:每个记录精确到一块内存区域,该区域内有跨代指针。其中卡精度指的是一种卡表的方式实现记忆集,最常见的。卡表最简单的形式可以只是一个字节数组,字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作“卡页”(Card Page)。一个卡页包含不止一个对象,只要有一个对象存在跨代指针,则将对应卡表的数组元素值标记为1,称这个元素变脏,没有则标识为0。只要筛选出卡表变脏的元素就知道哪内存块包含跨代指针,加入GC Roots中一起扫描。
    15. 写屏障,在每一条引用对象赋值后,调用写屏障的方法,修改卡表状态。
    16. Serial收集器(新生代收集器),单线程工作,必须暂停其他所有工作线程,是HotSpot虚拟机运行在客户端模式下的默认新生代收集器,有着优于其他收集器的地方,那就是简单而高效(与其他收集器的单线程相比),对于内存资源受限的环境,它是所有收集器里额外内存消耗(Memory Footprint)最小的;对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择。
    17. ParNew收集器(新生代收集器)实质上是Serial收集器的多线程并行版本。
    18. Parallel Scavenge收集器(新生代收集器),标记-复制算法,“吞吐量优先收集器”。如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。自适应调节策略。
    19. Serial Old(老年代收集器),单线程。也可以作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。
    20. Parallel Old(老年代收集器),多线程并发收集,标记-整理,吞吐量优先。
    21. CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。基于标记-清除算法。(1)初始标记(CMS initial mark)标记一下GC Roots能直接关联到的对象,速度很快 (2)并发标记(CMS concurrent mark)从GCRoots的直接关联对象开始遍历整个对象图的过程(3)重新标记(CMS remark)修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(4)并发清除(CMS concurrent sweep)清理删除掉标记阶段判断的已经死亡的对象。其中初始标记、重新标记需要停止全部进程。占用处理器线程,使吞吐量下降,尤其是低核心情况下。给予标记-清除算法会产生大量碎片,当无法分配足够大的内存空间会导致fullgc。
    22. Garbage First(简称G1)(老年代新生代均可)收集器,标记-整理算法,面向局部收集,基于Region的内存布局形式。把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。大对象存放在Humongous Region。跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,优先回收价值大的Region。每个Region都维护有自己的记忆集,这些记忆集会记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围之内。每一个Region设计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。回收步骤:初始标记(Initial Marking):标记GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的。并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。占用内存以及而外执行负载较cms高。小内存cms好一些,大内存情况下G1更强,6-8GB内存是相差不多。
    23. Shenandoah收集器(低延迟垃圾收集器),基于Region的堆内存布局,也有着用于存放大对象的Humongous Region,不使用分代收集,用名为“连接矩阵”(Connection Matrix)的全局数据结构来记录跨Region的引用关系,降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题。初始标记(Initial Marking):标记与GC Roots直接关联的对象,Stop The World。并发标记(Concurrent Marking):遍历对象图,标记出全部可达的对象,这个阶段是与用户线程一起并发的。最终标记(Final Marking):处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集(CollectionSet)。会有一小段短暂的停顿。并发清理(Concurrent Cleanup):用于清理那些整个区域内连一个存活对象都没有找到的Region。并发回收(Concurrent Evacuation):是Shenandoah与之前HotSpot中其他收集器的核心差异。Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之中。通过读屏障和被称为“Brooks Pointers”的转发指针来解决在复制的同时并发访问的问题。初始引用更新(Initial Update Reference):需要把堆中所有指向旧对象的引用修正到复制后的新地址,这个操作称为引用更新。引用更新的初始化阶段实际上并未做什么具体的处理,设立这个阶段只是为了建立一个线程集合点,确保所有并发回收阶段中进行的收集器线程都已完成分配给它们的对象移动任务而已。会产生一个非常短暂的停顿。并发引用更新(Concurrent Update Reference):真正开始进行引用更新操作,这个阶段是与用户线程一起并发的。按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为新值。最终引用更新(Final Update Reference):解决了堆中的引用更新后,还要修正存在于GC Roots中的引用。这个阶段是Shenandoah的最后一次停顿。并发清理(Concurrent Cleanup):经过并发回收和引用更新之后,整个回收集中所有的Region已再无存活对象,这些Region都变成Immediate GarbageRegions了,最后再调用一次并发清理过程来回收这些Region的内存空间,供以后新对象分配使用。重点关注三个最重要的并发阶段(并发标记、并发回收、并发引用更新)。使用了读屏障来实现Brooks Pointer。
    24. ZGC收集器,基于Region的堆内存布局。Region具有动态性——动态创建和销毁,以及动态的区域容量大小,具有大、中、小三类容量。小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象。大型Region在ZGC的实现中是不会被重分配的,因为复制一个大对象的代价非常高昂。ZGC的染色指针技术继续盯上了这剩下的46位指针宽度,将其高4位提取出来存储四个标志信息。通过这些标志位,虚拟机可以直接从指针中看到其引用对象的三色标记状态、是否进入了重分配集(即被移动过)、是否只能通过finalize()方法才能被访问到。(1)并发标记(Concurrent Mark):遍历对象图做可达性分析的阶段,短暂暂停。标记阶段会更新染色指针中的Marked 0、Marked 1标志位。(2)并发预备重分配(Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。标记过程是针对全堆的。(3)并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。得益于染色指针的支持,ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。这样做的好处是只有第一次访问旧对象会陷入转发,也就是只慢一次。还有另外一个直接的好处是由于染色指针的存在,一旦重分配集中某个Region的存活对象都复制完毕后,这个Region就可以立即释放用于新对象的分配(但是转发表还得留着不能释放掉),哪怕堆中还有很多指向这个对象的未更新指针也没有关系,这些旧指针一旦被使用,它们都是可以自愈的。(4)并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,ZGC的并发重映射并不是一个必须要“迫切”去完成的任务。很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,节省了一次遍历对象图的开销。一旦所有指针都被修正之后,原来记录新旧对象关系的转发表就可以释放掉了。

    相关文章

      网友评论

        本文标题:JAVA基础之内存回收

        本文链接:https://www.haomeiwen.com/subject/xhwmtrtx.html