前言
上篇文章谈到了对象是如何进入到堆中的,今天这篇文章继续探讨对象的生命周期
一、堆的内存划分
堆里面分为年轻代和老年代,分别占堆内存的1/3和2/3|
对于年轻代,当空间占用达到整个年轻代大小的60%时会触发Minor GC。
而对于老年代,则依旧是当空间占用达到整个堆大小的70%(可以通过参数-XX:MaxHeapFreeRatio来控制)时会触发Full GC。
一般MinorGC会相对FullGC会快很多,如何减少FullGC也是我们JVM优化的重点 ,因为MinorGC主要堆年轻代进行回收,内存小,而fullGC是对整个堆的内存进行回收,而且年轻代和老年代默认采用的算法不同,年轻代一般采用复制算法,老年代采用标记整理算法
二、垃圾回收算法
1.标记-复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
2.标记-清除算法
算法分为“标记”和“清除”阶段:标记存活的对象,统一回收所有未被标记的对象(一般选择这种);也可以反过来,标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,比较简单,但是会带来两个明显的问题:
1.效率问题(如果需要标记的对象太多,效率不高)
2.空间问题(标记清除后会产生大量不连续的碎片)
3.标记-清除算法
根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
三、执行流程
在年轻代达到一定阈值后,会进行一次minorGC
GC过程:如果对象在一次GC后仍存活,会从Eden进入年轻代的S1区域,同时GC年龄加1,再GC后仍存活会放入S2,然后在S1和S2里面往复,当年龄达到15时就会进入老年代,而老年代在内存达到阈值时就会进行FullGC
四、垃圾回收算法的选择
1.年轻代(标记-复制算法):JVM里面大多数对象都是用完即丢的,所以minorGC一般会发生的比较频繁,那这个时候就需要效率相对高的算法——标记-复制算法,复制算法的缺点很明显,就是内存的利用效率不高,需要预留内存来存储存活对象,根据大多数对象不会存活的逻辑,其实只需要预留一小块对象给存活对象就行,同时,Survivor区的作用是存储在上一次Minor GC中幸存下来的对象,而且每次Minor GC之后,所有Survivor区里的对象都会被移动到另外一个Survivor区或者老年代中。如果Survivor区过大,那么每次GC操作时需要复制的对象也会增多,耗费更多时间和空间。于是,年轻代中的内存占比为8:1:1,当然这是一个经验值,有需要可以自己做调整
2.老年代(标记-整理算法):老年代的对象生命周期相对较长,而且,大多数对象是不会被回收掉的,如果采用复制算法,需要复制的对象就会很多,效率就会降低;如果使用标记清除算法,则会产生很多不连续的内存空间,导致内存分配时需要遍历整个堆来查找足够大的连续空间,从而降低了垃圾回收的效率和性能。而且,上篇文章提到过,大对象会直接进入老年代,大对象意味着要大片的连续存储空间,所以选择的算法要能够清理内存碎片——标记-整理算法
五、如何确定对象是否可以被回收
JVM提供了两种算法来确定对象是否为垃圾对象
1.引用计数法
这种⽅式是给堆内存当中的每个对象记录⼀个引⽤个数。有引用就+1,引⽤个数为0的就认为是垃圾。这是早期JDK中使⽤的⽅式。引⽤计数⽆法解决循环引⽤的问题。
引用计数算法也存在一些问题。例如,在多线程环境中,如果多个线程同时修改同一个对象的引用计数,就可能出现竞争问题
1.可达性算法
该算法从一组被称为“GC Roots”的根对象开始遍历内存中的所有对象,并标记出所有能够被访问到的对象。未被标记的对象则会被认为是无用的垃圾对象,可以被回收。
以下四种情况下的对象被视为“GC Roots”,即垃圾回收器扫描的起始点:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
如果一个对象与任意“GC Roots”都没有关联,则被认为是不可达的,即它已经死亡,可以被回收。而如果一个对象至少与一个“GC Roots”存在关联,则被认为是可达的,不能被回收。
五、循环引用对象如何回收
当两个对象之间存在相互引用的情况时,即使它们与“GC Roots”之间没有直接联系,也不会被判定为不可达。此时,JVM需要通过其他方式来确定这些对象是否为垃圾对象。
在使用基于可达性分析算法的垃圾回收器中,JVM会遍历所有已经标记为可达(reachable)的对象,并将其保留下来,未被标记的则认为是垃圾对象(garbage)。而对于循环引用的情况,如果两个或多个对象彼此之间形成了一个环状结构,那么它们的引用计数都不会降为零,因此无法通过引用计数算法判断这些对象是否为垃圾对象。
针对循环引用的问题,一种常见的解决方案是使用弱引用(weak reference)机制。弱引用指向的对象只有在存在强引用(strong reference)同时指向该对象时才会被保留下来,在其他情况下,即使存在相互引用,也可以被垃圾回收器自动清理掉。这样就能够有效地处理循环引用的情况
如果觉得有用,欢迎点赞收藏,一起学习一起进步。
总结
这篇文章对于堆中垃圾回收机制做了简单的阐述,并对堆中分代模型下选择不同的垃圾回收算法做了解释,下篇文章将继续探讨一下各种垃圾回收器,比如:Parallel GC,CMS,G1等