JAVA虚拟机的垃圾回收机制

时间:2022-01-25 00:03:33

1.垃圾回收器确定对象需要被回收采用的是可达性算法

通过 一系列 的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots 没有任何 引用链相连时(或者说从 GC Roots到这个对象不可达 ),则证明此对象是不可用的。

注意:不可到达的对象也并非是非死不可的。对象死亡必须标记两次,如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过(也就是说对象的finalize()方法只能被调用一次),虚拟机将这两种情况都视为“没有必要执行”。

    如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它(即去执行对象的finalize()方法,这里所谓的“执行”是值虚拟机会触发这个方法,但并不承若会等待它运行结束,主要是为了防止对象的finalize方法执行缓慢或发生死循环,导致其他对象不能被执行的,从而引起内存回收系统崩溃)。     finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只需要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。

2.垃圾回收算法

⑴标记-清除

①过程分为两个阶段: 第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。
②优缺点: 优点:简单,不会增加编译器或赋值函数的负担 缺点:要花费大量时间去标记和清除垃圾,而且产生大量不连续的内存碎片,导致之后分配较大对象时无法找到连续的内存而不得不触发另一次垃圾回收动作 JAVA虚拟机的垃圾回收机制

⑵复制算法 内存按容量划分为大小相等的两块每次只使用其中的一块。当这一块的内存使用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
复制算法的优点每次都是对整个半区进行内存回收,实现简单、运行也高效;在那块使用内存上进行内存分配时,不用考虑内存碎片的问题,只要移动堆顶指针,按顺序分配内存即可;缺点将内存缩小为原来的一半,代价较高。JAVA虚拟机的垃圾回收机制
改进的收集算法(目前用来回收新生代) 将内存划分为一块较大的Eden空间和两块较小的Survivor空间每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor空间,最后清理掉Eden和刚才用过的Survivor空间,清理完成后,刚刚被清理的Eden和另一块在回收时放入存活对象的Survivor空间作为使用内存,刚被清理的Survivor作为保留空间,以便后面用来回收之用。
改进的收集算法的缺点:那块空的Survivor空间能否放得下Eden和使用的Survivor空间中还存活的对象,如果Survivor空间不够存放上一次新生代收集下来的存活对象,此时就需要向老年代“借”内存,那些剩余未放下的对象就通过分配担保机制进入老年代。 ⑶标记-整理算法 此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
JAVA虚拟机的垃圾回收机制 ⑷分代收集算法 当前商业虚拟机都采用这个“分代收集”算法(Generation Collection),它根据对象存活周期的不同将内存划分为几块,一般是把java堆分为新生代和老年代,根据各个年代的特点选用不同的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,因此可以选用“复制算法”,也就是上面所说的"改进的收集算法",此时只需要付出少量存活对象的复制成本即可;对于老年代,因为对象存活率较高、也没有额外空间为期分配担保,就必须使用“标记-清除”或“标记-整理”算法来进行回收。
3.垃圾收集器 JAVA虚拟机的垃圾回收机制

1.Serial/Serial Old

Serial(复制算法)/Serial Old(标记-整理算法)收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

2.ParNew收集器其实就是serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与Serial收集器一样。除了Serial收集器只有它能和CMS一起工作。

3.Parallel Scavenge收集器(复制算法)也是一个新生代收集器,又是并行多线程收集器。parallel Scavenge收集器的特点是它的关注点与其他收集器不同,目标则是达到一个可控制的吞吐量。吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。并不会保证更快,因为设置小了会导致更频繁。

4.Parallel Old(并行GC)收集器   是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

5.CMS(并发GC)收集器,使用"标记-清除"算法,是一种以获取最短回收停顿时间为目标的收集器

6.G1收集器,使用“标记-整理”算法,是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

4.总结

垃圾回收主要针对的是堆区的回收,因为栈区的内存是随着线程而释放的。堆区分为三个区:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)。

 年轻代:对象被创建时(new)的对象通常被放在Young(除了一些占据内存比较大的对象),经过一定的Minor GC(针对年轻代的内存回收)还活着的对象会被移动到年老代(一些具体的移动细节省略)。

年老代:就是上述年轻代移动过来的和一些比较大的对象。Minor GC(FullGC)是针对年老代的回收

永久代:存储的是final常量,static变量,常量池。

直接调用system.gc()会立即停止所有线程并执行full gc。