??作者主页:
温文尔雅的清欢渡
?? 近期学习方向:性能调优
??欢迎 点赞 👍 收藏 ? 留言 📝 关注 ? 私聊我
前言
深入理解垃圾收集算法和垃圾收集器。
一、垃圾收集算法
在进行垃圾回收的第一步就是要判断对象是否存活。
判断对象是否存活的算法
①引用计数算法 给对象中添加一个引用计数器,当有一个地方引用它时,计数器就会+1;当变量被释放引用或者断开引用时,计数器就会-1;如果引用计数器变为0,就代表这个对象不存活了,会被垃圾回收。
优点:实现简单,效率高。 缺点:很难解决对象之间相互循环引用的问题。 如下面代码所示:父对象有一个子对象的引用,子对象反过来引用父对象。除了对象objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,所以导致它们的引用计数器不可能为0,于是引用计数算法不能回收他们。
public class GcTest {
Object instance = null;
public static void main(String[] args) {
GcTest object1 = new GcTest();
GcTest object2 = new GcTest();
object1.instance = object2;
object2.instance = object1;
object1 = null;
object2 = null;
}
}
②可达性分析算法 将“GC Roots” 对象作为起点,从这些节点开始向下寻找引用的对象,然后把找到的对象都标记为非垃圾对象。当所有的引用节点寻找完毕之后,其余未标记的对象都是垃圾对象,会被回收。
GC Roots根节点:虚拟机栈中引用的对象(本地变量)、方法区中类静态属性引用的对象(静态变量)、方法区中常量引用的对象、本地方法栈中引用的对象等等。
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。在Java语言中,将引用又分为强引用、软引用、弱引用、虚引用4种,这四种引用强度依次逐渐减弱。
- 强引用:普通的变量引用
无论引用计数算法还是可达性分析算法都是基于强引用而言的。
public static User user = new User();
- 软引用:将对象用SoftReference软引用类型的对象包裹,正常情况不会被回收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉。软引用可用来实现内存敏感的高速缓存。
public static SoftReference<User> user = new SoftReference<User>(new User());
- 弱引用:将对象用WeakReference软引用类型的对象包裹,弱引用跟没引用差不多,GC会直接回收掉,很少用
public static WeakReference<User> user = new WeakReference<User>(new User());
- 虚引用:虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,几乎不用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。
常用的垃圾收集算法
③标记-清除算法 分为“标记”和“清除”阶段:标记存活的对象, 统一回收所有未被标记的对象,反之也可。采用从GC Roots进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。 缺点: 1. 效率问题 (如果需要标记的对象太多,效率不高) 2. 空间问题(标记清除后会产生大量不连续的碎片)
④复制算法 为了解决标记-清除算法的问题,复制算法出现了。首先把内存分成大小相同的两块,对象面和空闲面,每次使用其中一块,当内存满了时,基于copying算法的垃圾收集就从GC Roots中寻找非垃圾对象,并将复制到空闲面,然后把对象面的垃圾对象都清理掉。最后,原来的空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。 缺点:浪费空间。
⑤标记-整理算法 采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收垃圾对象后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。 缺点:标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此效率不高。 优点:解决了内存碎片的问题。
⑥分代收集算法 核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域,不同区域也使用不同垃圾收集算法。一般情况下将堆区划分为老年代和新生代。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。注意:“标记-清除”或“标记-整理”算法会比复制算法慢10倍以上。
二、垃圾收集器
如果垃圾收集算法是方法论,垃圾收集器就是具体实现。我们要根据具体应用场景选择适合的垃圾收集器。 Serial、Parallel 、ParNew收集器:新生代采用复制算法,老年代采用标记-整理算法。 CMS: 标记-清除算法 Parallel是JDK8默认的垃圾收集器。 G1是JDK9默认的垃圾收集器。
Serial收集器(-XX:+UseSerialGC(年轻代), -XX:+UseSerialOldGC(老年代))
Serial是串行、单线程的收集器,只会使用一条垃圾收集线程,在进行垃圾收集时必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
Parallel Scavenge收集器(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))
Parallel是多线程的收集器,关注吞吐量和CPU资源(如何高效率的利用CPU)。默认的收集线程数跟cpu核数相同,当然也可以用参数(- XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。
ParNew收集器(-XX:+UseParNewGC)
ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
CMS收集器(-XX:+UseConcMarkSweepGC(老年代))
CMS(重点)是一种以获取最短回收停顿时间为目标的并发收集器,关注点更多的是减少用户线程的停顿时间来提高用户体验,让垃圾收集线程与用户线程基本上同时工作。 执行过程分为四个步骤: 初始标记(STW): 暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快。 并发标记: 从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。 重新标记(STW): 为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,主要用到三色标记里的增量更新算法做重新标记。 并发清理: 开启用户线程,同时GC线程开始对未标记的区域做清扫。如果有新增对象会被标记为黑色不做任何处理。 并发重置:重置本次GC过程中的标记数据。
CMS的相关核心参数
- -XX:+UseConcMarkSweepGC:启用cms
- -XX:ConcGCThreads:并发的GC线程数
- -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
- -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一 次5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比) 6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设 定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整 7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段 8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW 9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
优点:并发收集、低停顿。 缺点(重点): 1.CPU资源敏感:用户线程和垃圾收集线程会争抢资源; 2.会产生浮动垃圾:在并发清理阶段产生的垃圾,只能等到下一次gc再清理了; 3.空间碎片:收集结束时会有大量空间碎片产生,当然通过参数- XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理 ; 4. 并发失败:执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入STW,用serial old垃圾收集器来回收。
|