JVM 垃圾收集机制
作者:互联网
一、什么是GC,为什么需要GC
GC:Gabage Collection,即垃圾收集;
随着应用程序所应对的业务越来越庞大、复杂、用户量越来越多,没有GC就无法保证应用程序的正常进行,经常造成STW的GC又跟不上实际的需求,影响用户体验,因此也需要不断的对GC进行优化。
二、名词解析
1、OOM --内存溢出
内存溢出是指没有空闲的空间,并在垃圾收集后,依旧无法提供更多的内存空间。
2、 内存泄漏
内存泄漏是指对象已经不再被使用,但是GC无法对其进行回收。
例:
①:单例模式的对象,调本身以外的对象,在使用完之后,没有断开指针指向,因为单例模式的生命周期是和应用程序一样长的,如果没有断开,则引用的对象的生命周期和应用程序的周期一样长。
②:在进行资源使用时,如数据库连接,网络连接等,在使用完毕后,没有调用close()方法进行关闭连接。
3、STW
STW:stop - the - word,暂停所有的线程活动。
三、GC算法
1、标记阶段
引用计数算法 --java没有使用此算法,python使用
实现:每个对象都有专属的一个引用计数器,每当有个地方引用到了这个对象,则计数器加1,相反则减一,任何时刻引用计数器为0的对象不可能被使用。
优点:实现简单,垃圾对象便于辨别,判断效率高,回收没有延迟性。
缺点:增加了存储空间和时间的开销;无法处理循环应用的情况。
例:类1引用了类2,类的计数器加1,类2引用类3,类3引用类4,类4引用类2,类2计数器再加1,则类2的计数器为2
当类1取消了对类2 的引用,类2的计数器减为1,但此时类2及后面的类3,类4应该为无需引用的对象,需要进行垃圾收集,但是由于类4对类2的引用,导致类2的引用计数器为1,GC对类2以及类3,类4均无法进行垃圾收集,造成内存泄露。
可达性算法 -- java在使用
实现:存在一个GC root Set的集合,其中存放着一系列GC root,GC root就是以一个对象为根节点,连接其它对象,能被连接在树上的对象,称为可达对象,垃圾收集时不需要被收集;没有连接在书上的对象,称为不可达,GC会将其回收。
如图,Object 1、Object 2、Object 3在GC root上均可达,因此GC不会将其回收,Object 4、Object 5、Object 6没有连接在GC root上,为不可达对象,GC会将其回收。
可作为GC Root的对象:1、虚拟机栈中引用的对象(各线程被调用的方法中使用到的参数,局部变量等);
2、本地方法栈中引用到的对象;
3、方法区中类静态属性引用的对象(java类的引用类型静态变量);
4、方法区中常量引用的对象(字符串常量池中的引用);
5、被同步锁synchronized持有的对象。
2、清除阶段
①标记-清除算法 Mark-Sweep
执行过程:当有效空间被消耗完的时候,系统会停止整个程序STW(stop the word)
①标记:Collector从根节点引用开始遍历,标记所有被引用的对象,一般是在对象的Header中标记为可达对象;
②清除:Collector从堆内从头到尾进行线性遍历,如果发现某个对象在Header中没有标记为可达对象,则将其回收。
缺点:效率不算高(遍历实现) 需要停止整个程序,体验较差 清理出来的空间是不连续的,产生内存碎片,需要有一个维护一个空闲列表
注意:清除的含义:算法所谓的清除,是将没有引用的对象的地址,存放进空闲列表中,当有新的对象进入时,判断是否足够,如果够就进行存放。
②复制算法 Copying
执行过程:将内存区域分为俩个区域, 每次只使用其中一块,在进行垃圾收集时,将存活的对象复制到空闲的区域(是复制整个对象,不是地址),然后清楚正在使用的模块的所有对象,然后对调俩个对象的角色,继续执行。
优点: 运行效率高 没有碎片化问题
缺点: 需要俩倍的存储空间。 对G1这种拆封大量的region(区间)的GC,复制而不是移动,意味着GC需要维护region的关系(对象复制后,地址改变,需要修改堆中指向对象的地址),不管是内存占用还是时间,开销都不小。
注意:如果系统中的垃圾对象非常多,才能效率高,复制算法的存活率不能太高,否则耗时费力不讨好。(所以适合用在新生代,不适合用在老年代)
③标记-压缩(整理)算法 Mark-Compact
执行过程:当有效空间被消耗完是,系统会停止整个程序
①标记:Collector从根节点引用开始遍历,标记所有被引用的对象;
②压缩:Collector从堆内从头到尾进行线性遍历,将标记为可达对象的对象,进行整理压缩,修改成为形成一个连续的存储空间,将不存活的对象进行清除。
优点: 消除了碎片空间的问题 不需要俩倍的存储空间
缺点:效率上是低于复制算法的 在移动对象时,需要维护region的关系,需要调整堆中对象的引用地址 需要STW
④ 增量收集算法(了解)
实现原理:让程序线程和垃圾收集线程交替执行,每次垃圾收集线程只收集一小片区域的内存空间,接着切换回应用程序线程,依次反复,直至完成垃圾收集。
增量算法的基础还是以标记-清除算法和复制算法的,更多的是通过线程的交替执行,解决线程间的冲突问题,允许垃圾收集线程以分阶段的方式进行,解决STW时长过长的问题
缺点:线程间的来回切换和上下文的切换会增加消耗,会使得垃圾收集的总体成本上升,降低系统的吞吐量。
⑤代收集算法(了解)
分新生代和老年代,用不同的算法进行垃圾收集
⑥分区算法(了解)
实现原理:将堆空间,分成不同的小区间,在优先的时间内,对逐个小区间进行垃圾回收,实现减小STW时间的目标。
四、引用
1、强引用
最传统的“引用”的定义,是指在程序代码中普遍存在的引用,即类似Object object = new Object();
无论在什么情况下,只要是强引用的关系,垃圾收集齐永远不会回收掉引用的对象。
大部分场景下使用,也是造成内存泄露的主要原因
2、软引用
在系统即将发生内存溢出之前,将这些对象列入回收范围中进行第二次回收,即被列入回收范围的对象的引用就是软引用。如果内存够用,软引用的对象不会被回收,如果内存不够则回收。用于缓存的场景,如高速缓存。
3、弱引用
被弱引用关联的对象,只能生存的到下一次垃圾回收之前,当垃圾收集器工作时,无论空间是否足够,都会回收被弱引用的关联的对象。
用于缓存的场景。
4、虚引用
一个对象是否有虚引用的存在对其生存时间都没有影响,对虚引用的引用,是为了能在这个对象呗收集器回收时收到一个系统通知。
用于对象回收跟踪的场景。
总结
在引用存在的情况下:
强引用(Strong reference):必定不会被回收
软引用(soft reference):内存够时不会被回收
弱引用(week reference):生存下来,但是下次垃圾回收的时候,无论内存是否够用,一定会被回收
虚引用(phantom reference):只为了跟踪的目的,对生存无影响。
五、垃圾回收器
1、评价GC的性能指标
吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间 = 用户代码+内存回收代码)
暂停时间:执行垃圾收集时,程序被暂停的时间(越来越重要)
内存占用:java堆区所占的内存大小(内存的扩大,会带来负面影响)
主要考虑因素是:吞吐量和暂停时间,垃圾算法只能考虑吞吐量和暂停时间俩个之一
现在的标准:在最大吞吐量优先的情况下,降低停顿时间
2、并行、并发、串行
串行:不同线程在不同时间段切换执行。
并行:不同线程在同一个时刻同时执行。
并发:不同线程,在同一个时间段内,来回切换执行。
3、收集器
1、serial 收集器 串行回收器
serial 收集器用于新生代的垃圾收集器,采用复制算法、串行回收和有STW的方式执行内存回收
serial Old 收集器用于老年代的垃圾收集器,采用标记-压缩算法、串行回收和有STW的方式执行内存回收
2、ParNew 收集器 并行回收器
par为parallel 的缩写,new表示只适用于新生代的垃圾收集。
ParNew 收集器采用复制算法、并行回收,有STW的方式执行内存回收。
3、Parallel 收集器 并行回收器
Parallel 收集器采用复制算法、并行回收,有STW的方式执行内存回收;Parallel有着自适应的调节策略;
ParallelOld 收集器用于老年代的垃圾收集。
4、CMS收集器 --已经淘汰 并发回收器
CMS 收集器收集器采用标记-清除算法、并行回收,有STW的方式执行内存回收;
CMS收集器有着低延迟性,关注的是尽可能的缩短垃圾收集时用户线程的停顿时间,但存在着空间碎片问题。
工作阶段:
1、初始标记阶段:有STW,阶段的主要任务是标记处GC root能直接关联到的对象。
2、并发标记阶段:无STW,阶段的工作从GC root的直接关联对象出发开始遍历整个对象图的过程,这个过程耗时长,但是没有STW,并发运行。
3、重新标记阶段:有STW,阶段的主要工作是修正并发标记阶段因为用户程序继续运作从而导致标记产生变化的那部分对象的标记记录。即修改因为上一个阶段产生改变的对象的标记。
4、并发清理:没有STW,并发进行,阶段的工作是清理删除标记阶段判断的已经死亡的对象,释放空间。
初始标记阶段和重新标记阶段虽然有STW,但是暂停的时间都很短,所以降低了延迟,而需要大量时间的标记和清理删除则是并行进行,所有无影响。
在CMS回收过程中,还应该确保有足够的内存可用,所以cms和其他等到老年代快被填满在进行收集不同,而是当堆内存的使用率达到了某一阈值,便开始进行回收。
不使用标记-压缩的算法:用户线程还在进行,所以得保证数据地址的一致。
5、G1收集器 并发回收器
G1 回收器,将堆划分为一个个小的region,每个region可能作为新生代,可能作为老年代,在回收时,G1 回收器,可以每次回收部分region,减少STW的时间。
region存放:
区域回收过程:
年轻代GC(Young/Minor GC)频率很高
老年代并发标记过程(Concurrent Marking 和Major GC不同,不会回收整个老年代,只会回收部分价值比较高的空间)
混合回收(Mixed GC)
如果需要,单线程、独占式、高强度的FUll GC还是继续存在的,它针对GC的评估失败提供一种失败保护机制,即强力回收)
回收过程(了解)
一、总体阶段
1、扫描根;
2、更新set:主要处理老年代对新生代的引用,RSet可以准确的反应老年代对所在内存分段中对象的引用。
3、处理set;
4、复制对象(堆空间回收);
5、处理引用:Eden区清空以后,GC停止工作;
二、并发标记过程
1、初始标记阶段:根节点触发直达的对象;
2、根区域扫描:扫描Survivor可直达老年代的区域对象,并标记;这一过程必须在yang GC之前完成。
3、并发标记: 对整个堆进行标记,此过程可能被yangGC打断,若发现这个区域的对象都是垃圾,那这个区域会马上进行回收。标记过程中同时计算对象的活性
4、再次标记:因为是并发执行,所以对上一个环节需要进行修正。
5、独占清理:计算各个区域的存活对象和GC回收比例,并进行排序,,识别可以混合回收的区域,为下一阶段做准备,此过程是STW的,但是不会实际上去做垃圾的收集。
6、并发清理阶段。
三、混合回收
当越来越多的对象晋升老年代,为了避免内存耗尽,虚拟机会触发一个混合的垃圾收集器,即Mix GC,除了回收全部的年轻代,还会回收部分的老年代。
G1的初衷是避免Full GC的出现。
6、总结
各收集器的搭配使用
其中,红色虚线为已经废弃的收集器搭配,绿色虚线为即将废弃的收集器搭配,CMS在1.9已经被废弃,现存的搭配如下:
新生代:Serial GC 老年代:Serial Old GC
新生代: Parallel GC 老年代:Parallel GC
新生代+老年代:G1 GC
收集器的属性
标签:收集,收集器,对象,标记,回收,GC,垃圾,JVM,引用 来源: https://www.cnblogs.com/caijilin/p/16357666.html