垃圾收集器怎样对待引用类型
垃圾收集器不会回收强引用 类型;
在系统要发生内存溢出异常前,垃圾回收器会将 软引用 回收,若内存仍不够用,则抛出内存溢出异常;
下一次垃圾回收发生时,垃圾回收器会回收弱引用指向的内存;
虚引用 对垃圾回收不产生影响,只是为了使对象在被垃圾回收时会接收到一个通知。
找到存活的对象
安全点:向OopMap存放记录的位置
安全区域:在某一段代码中,引用关系不会发生变化。
记忆集(RememberedSet):用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。用来减少Gc Roots的扫描范围。
卡表:记忆集的一种实现,最简单的实现是字节数组。
卡页:字节数组中的元素,标识着内存中的一块特定大小的内存,被标识的这个内存称作卡页。
如果卡页中的一个或多个对象的字段存在跨代指针,则指向这个内存的元素(也就是字节数组中的一个元素)会标识为1。这样通过检查卡页中为1的元素就可以轻易得出哪些内存块中存在跨代引用,并将其加入GcRoots中一并扫描。
G1 的 记忆集实现:一种HashMap,key是别的region的起始地址,value 是 卡表的索引号。每个Region都有自己的记忆集。
根结点枚举: 查找根结点:活跃线程用户处于安全点时,在OopMap中存放对象的引用。
查找引用链的过程
三色标记的方法。
对象消失的问题存在当切仅当 ,赋值器增加了黑色到白色的引用 且 赋值器删除了所有灰色到白色的引用。
G1按照原始快照的方式避免对象消失的问题。当灰色对象要删除只想白色对象的引用关系时,就要将这个删除的引用记录下来,在并发扫描结束后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。
CMS采用增量更新的方式解决对象消失的问题。当黑色对象插入指向白色对象的引用关系时,将这个黑色对象记录下来,等并发扫描结束后,再重新扫描一次这些褐色对象。
垃圾收集发生时如何让用户线程在安全点停下
主动式中断,垃圾收集需要中断线程时,设置一个标志位,用户线程在执行时会轮询这个标志位,一旦标志为真时,就将自己在最近的安全点上主动中断挂起。【通过一条汇编指令test 完成这个功能】
阻塞或者Sleep的线程,在安全区域退出时,会检测垃圾收集器是否完成,未完成则该线程阻塞等待,完成则继续执行。
垃圾收集算法
标记-清除
标记存活的对象,清除未标记的对象。 会产生大量不连续内存碎片。分配大对象时,容易找不到连续的大内存区域,从而提前引发下一次垃圾回收操作。
标记 - 复制
使用 新生代的 Eden 和 一个 Survivor 内存区域分配对象,另一个Survivor 用作垃圾回收时的复制区域。
标记-整理
标记存活的对象,然后将存活的对象都向内存空间的一段移动。
Serial /Serial Old / ParNew /Parallel Scavenage/Parallel Old 垃圾收集器
这些垃圾收集器,在发生垃圾收集时,都会暂停所用的用户线程。
Serial 新生代垃圾收集器,采用标记-复制算法对新生代进行垃圾收集,垃圾回收的线程为单线程模型。
Serial Old 老年代垃圾收集器,采用标记 -整理 算法 对老年代进行回收,垃圾回收的线程为单线程模型。
ParNew ,新生代垃圾收集器,Serial的多线程版本。
Parallel Scavenage ,新生代垃圾收集器,,在 ParNew 的基础上 支持对吞吐量的精确控制。通过控制最大的内存收集停顿时间 以及 直接设置吞吐量的大小来实现。 吞吐量 = 用户代码执行时间/(用户时间+垃圾收集时间)的比例。
Parallel Old, 老年代垃圾收集器, Parallel Scavenage的老年代版本,采用 标记 - 整理 算法 实现。
如何实现根结点枚举 和 可达性分析 以及清除过程
一次性的完成了根结点枚举 和 可达性分析 以及 内存回收的过程。
CMS收集器,老年代的收集器【最短停顿回收垃圾收集器,java9已经标记为弃用】
基于标记 - 清除算法实现,配合新生代的ParNew共同管理
如何实现根结点枚举 和 可达性分析 以及清除过程
初始标记GcRoots直接关联的对象 -> 与用户线程并行,并发的进行遍历对象图的过程(线程数为(处理器核心线程数+3)/4)-> 重新标记(修正第二阶段的变化)-> 与用户线程并行,并发的对未使用的对象进行清除。
(G1)GarbageFisrt垃圾收集器
混合的垃圾收集模式,发生回收时,衡量标准不再是内存属于那个分代,而是判断哪块内存中存放的垃圾最多,回收收益最大。也就是说,Region是垃圾回收的最小单元。允许用户设置内存收集的停顿时间-XX:MaxGcPauseMillils。
G1将内存 划分为 多个大小相等的独立Region 区。
依然保留新生代和老年代的概念,在此基础上增加了一类特殊的Humongous区域【专门存放大对象,判断上是超过region大小的一半,对于超过整个region大小的对象,会为其分配多块连续的Region】。
每个Region根据需要动态扮演新生代和老年代的角色。
如何实现根结点枚举 和 可达性分析 以及清除过程
初始标记仅标记GcRoots能直接关联的对象,并修改TAMS的值。(需要短暂停顿用户线程 ) ->
并发标记,进行可达性分析,这个阶段与用户线程并行,耗时较长。这个过程中分配的对象存放在region的TAMS指针的上方。->
最终标记,短暂暂停用户线程,处理上个阶段新生成的对象引用 ->
筛选回收,更新region的统计数据,对各个region的回收价值和成本进行排序,根据用户设定的期望停顿时间,自由的选择任意多个region构成回收集,然后把决定回收的集合的存活对象复制到空的region中,再清理掉整个旧region的全部空间。这个阶段暂停用户线程,由多个收集器线程并行完成。
ZGC
ZGC 是 一款基于region内存布局,不设分代的,使用了读屏障,染色指针和内存多重映射等技术实现可并发的 标记-整理算法的,以低延迟为目标的一款垃圾收集器。
region是动态创建,销毁,动态分配大小的。
染色指针:Linux 64位机器支持46位的物理地址寻址空间,47位进程虚拟地址空间。ZGC将46位物理寻址空间的高4位提取出来,用作记录对象的三色标记状态(mark0,mark1),是否进入重分配集,是否只能通过finalize方法访问。这样也导致ZGC能管理的内存不能超过4TB。
内存多重映射:把染色指针的高4位看作是分段符,将这些不同的段号映射到同一块物理内存空间。然后经过多重映射转换后,就可以利用染色指针进行正常寻址了。
如何实现根结点枚举 和 可达性分析 以及清除过程
以下各阶段都是和用户线程并行的。
短暂暂停用户线程,对GcRoots直接关联对象进行最初标记,然后经历并发标记完成可达性分析阶段,然后经过最终标记的短暂暂停。【并发标记】
扫描所有的region,得到本次收集过程需要清除的region,并将这些region 组成重分配集。【并发预备重分配】
将重分配集中存活的对象复制到新的region中,并为重分配集中的每个region维护一个转发表,记录从新对象到旧对象的转发关系。 当用户线程并发的访问到了处于重分配集上的对象时,这次访问会被预制的内存屏障所截获,然后立即根据转发表,将访问转发到新复制的对象上,并同时更新该引用的值。【并发重分配】
并发重映射,将修正整个堆中指向重分配集中就对象的所有引用,根据转发表改到新复制的地址。这个阶段在下次垃圾回收器并发标记访问对象图时发生。【并发重映射】