JVM垃圾收集策略

JVM垃圾收集器

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来

哪些内存需要回收?

垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中,哪些还“存活”,哪些已经死去,这里有两种常用的判断算法,引用计数算法和可达性分析算法。

  • 引用计数算法:Python使用了该算法,其原理为给对象中添加一个计数器,每当一个地方引用它时,计数器加1,引用失效时,计数器减1;任何时刻计数器为0的对象就是不可能再被使用的。但是一个缺点是无法解决对象之间相互循环引用的问题。如下图,objA中引用objB,objB引用objA。
    在这里插入图片描述
  • 可达性分析算法:被Java、C#等语言所使用的,这个算法的基本思路是通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链想连时,则证明此对象是不可用的。
    在这里插入图片描述
    在Java中,可作为GC Roots的对象包括下面几种:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(Native方法)引用的对象

死亡前的自救

即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它们暂时出于“缓刑”阶段,真正宣告一个对象死亡,至少经历两次标记过程。
如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记,并判断该对象是否有必要执行finalize()方法;
如果有必要执行finalize()方法,那么对象将会放置在一个叫做F-Queue的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级Finalizer线程去执行它。
这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。finalize()是对象逃脱死亡命运的最后一次机会,如果再finalize()中对象与任何一个对象建立了联系,那么在接下来的第二次标记时,就会被移出“即将回收”的集合。


垃圾收集算法

  • 标记-清楚算法:分为“标记”和“清楚”两个阶段;有两个缺陷,一是效率不高,二是空间碎片太多
    在这里插入图片描述
  • 复制算法:,它将可用内存按容量划分为大小相等的两部分,如图所示分为A、B两个区,当A区域全部用完时,把存货的对象复制到B区,然后一次性清除掉A区,此时,内存分配时就不再考虑内存碎片等复杂情况了。而且新生代中的对象98%是朝生夕死的,并不需要按照1:1来划分内存空间。Hotspot虚拟机博人Eden和Survivor的大小比例是8:1,每次使用Eden和其中一块survivor,回收时,将Eden和Survivor中还存活着的对象一次性地复制到另一个Survivor空间上,然后清除Eden和刚才用过的Survivor空间。如果Survivor空间不够用时需要依赖其他内存(老年代)进行分配担保。
    在这里插入图片描述
  • 标记-整理算法:如果对象存活率较高时,需要进行较多的复制操作,效率会变低。所以在老年代不选用这种算法。标记-整理算法的标记过程与标记-清理算法一致,但是清理过程是让所有存活的对象都向一端移动。
    在这里插入图片描述

HotSpot的算法实现

枚举根节点

可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表),由于方法区庞大以及在进行可达性分析的时候需要“Stop the world”,因此要求虚拟机在枚举根节点的时候需要快速。
HotSpot虚拟机是准确式GC(虚拟机知道内存中存储的是地址还是数),所以当执行系统停顿下来的时候,虚拟机有办法直接得知哪些地方存放着对象引用。HotSpot使用一组称为OopMap的数据结构来记录对象内什么偏移量上是什么类型的数据计算出来。

安全点

  • 安全点的选取:如果在每一条指令都生成对应的OopMap,那将会需要大量的额外空间,因此,HotSpot只在特定的位置记录这些信息,这些位置称为安全点。安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint。
  • 多线程同步停止:有两种方式,抢先式中断和主动式中断;抢先式中断在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上,但是这种方法没有被主流虚拟机采用;主动式中断的思想是当GC需要中断线程的时候,仅简单的设置一个标志,各个线程主动去轮训这个标志,发现中断标志位真时就自己中断挂起。轮训标志的地方和安全点是重合的。

安全区

如果线程处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全的地方去中断挂起,这时候需要安全区。所谓安全区就是指在一段代码片段中,引用关系不会发生变化,在这个区域中的任意地方开始GC都是安全的。
在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,JVM就可以发起GC;当线程离开Safe Region时,它要检查系统是否完成了根节点的枚举(或是整个GC过程)。


垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
在这里插入图片描述

Serial收集器

在这里插入图片描述
只使用一个CPU或一条收集线程去完成垃圾收集工作,必须暂停其他所有的工作线程,直到它收集结束。

ParNew收集器在这里插入图片描述

Serial收集器的多线程版,因为除了Serial收集器,目前它只能与CMS收集器配合工作。

Parallel Scavenge收集器

Parallel Scavenge收集器的关注点在与达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是运行用户代码的时间/CPU总消耗时间的比值。
在这里插入图片描述
Parallel Scavenge收集器的老年代版

CMS收集器

是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于“标记-清除”算法实现的,整个过程分为4个步骤,包括:初始标记、并发标记、重新标记、并发清除
在这里插入图片描述
初始标记很快,只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
耗时最长的并发标记和并发清除过程收集器线程可以与用户线程并行工作,所以整体上说,CMS收集器的内存回收过程与用户线程一起并发执行。
CMS收集器有一下两个缺点:
1. CMS收集器对CPU资源非常敏感,会占用CPU数量,降低程序的运行速度,在CPU数量低时尤其明显
2. 无法处理浮动垃圾,所谓浮动垃圾就是CMS在进行并行清理的时候,伴随程序运行而产生的新的垃圾,这部分垃圾只能留待下一次GC时清理掉。
3. 手机结束后会有大量空间碎片产生

G1收集器

在这里插入图片描述
G1收集器可以不需要与其他收集器配合就能独立管理整个GC堆,但它依然采用不同的方式去处理不同代的内存空间;另外G1是基于“标记-整理”算法实现的收集器,分配大对象的时候不会触发下一次GC;可预测停顿时间。
对于G1收集器而言,新生代和老年代不再是物理隔离的,G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的回收时间,优先回收价值最大的Region;
同时G1使用Remembered Set来避免全堆扫描,G1中每个Region都有一个对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会检查Reference引用的对象是否是处于不同的Region之中,如果是就把相关引用信息记录在被引用对象所处的Region的Remembered Set中,在进行内存回收时,在GC根节点的枚举范围中加入Remembered Set避免全堆扫描。

(本文总结于深入理解Java虚拟机)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值