大三学生,最近在学习线程,里面讲垃圾回收器Garbage Collection(以下简称GC)是守护线程最广泛的应用,听了这么久的GC居然没有好好地了解过,赶紧过来补充知识!看了一些前辈写的博客,融汇了网友们的知识,总结了这篇文章!
文章要点一览:
1.GC定义;
2.GC算法;
3.GC类型。
什么是垃圾?
垃圾(对象),就是没有被引用的对象,简单地可以理解为就是值为null的对象和超出作用域的对象,比如循环内定义的,放外面就用不了。
如何找到垃圾?
GC roots是一组称为GC root的必须活跃的引用,以这些引用为根节点,遍历与这些引用存在引用关系的对象。能被遍历到的(可达的)对象被判定为存活,其余对象(不可达的)被判定为死亡。(!!并不会直接回收空间!!)
如何清理垃圾?
并不是GC roots遍历一次就直接清除,第二次遍历的时候再看它的finalize()方法有没有被重写或者被调用,没被重写了或者被调用了,会直接被回收掉;否则的话就再给它一次机会,因为它可能通过它自己写的finalize()进行自救,所以再把它重新连接到GC roots上。
Java中的native本地方法,由于不是由java语言写的,所以java无法对其进行释放操作。C语言编写的
什么时候触发垃圾回收器工作呢?由于GC线程的优先级低,所以一般得不到运行机会:
1.system.gc()方法被调用的时(该方法建议GC执行full gc(后面有提到)命令,但不一定会执行) 。
2.内存不足时。
3.Java虚拟机处于空闲循环的时候。
以上条件时,GC会暂停所有用户线程(stop the world)并自动检查每一块分配出去的内存空间,然后自动回收无用的内存块。
为什么回收的时候要暂停所有用户线程呢?
因为在遍历GC roots时,用户线程的操作会影响引用关系,最后遍历出来的对象可能就不准确了,所以要暂停所有的,等到垃圾回收完再恢复。
1.标记-清除算法Mark-Sweep 顾名思义,两部分,标记,清除
标记:把GC roots遍历一遍,对遍历到的都做一个标记,一般是在对象的header区域。
清除:把没打上标记的全都清理掉。
缺点: 1.会产生不连续的空间碎片,如果这些碎片没有一个可以放下新来的对象,就会触发full gc(对整个堆进行垃圾回收)。
2.效率低下,内存中的对象实例数量非常多,遍历一遍耗时,暂停用户线程时间就会长,用户体验差。
2.标记-整理算法Mark-Compact 解决上一个算法空间不连续的问题
标记过程和上一个算法一样,但是清理的时候就不一样了。它是把没被标记对象挪到内存的指定一端,遍历完成后最后把这一端没被标记的全部清理。
优点:没有空间碎片,利用率提高。
缺点:算法效率低,不仅要对所以对象进行遍历标记,还要进行整理。
适合对象存活率高,可挪动的少的老年代。
3.复制算法
把内存空间分成两块,一块正常使用,一块预留。在执行垃圾回收的时候,把使用的那块存活的对象复制到预留的那块,原来那一块整体清除,这两块实现角色交换,交替使用。
优点:提高效率,无须遍历,不存在空间碎片。
缺点:空间浪费明显,如果对象存活率高,复制过程也极其费时。
适合对象存活率低的新生代。
★分代收集算法: 新生代young generation(复制算法)和老年代old generation(标记-整理算法):
分代的理由是优化gc的性能。如果没有分代,调用垃圾回收器回收时,要扫描堆中的所有区域,而很多对象是朝生夕死的,如果分代的话可以把新创建的对象集中放到某一个地方,垃圾回收时可以先把这一块地方的对象回收,这块地方叫做新生代。而长期存活的对象集中放到一个地方进行处理,这块叫做老年代。
因为年轻代的对象朝生夕死的比例高,所以采用复制算法。
而老年代存活对象多,用标记整理算法更好。
复制算法在新生代:
HotSpot VM把年轻带分为了三部分:Eden和两个survivor,分别叫from和to。默认比例8:1.新创建的对象会被分配到Eden区,这些对象如果经过第一次minor gc(就是对新生代进行GC)后仍然存活,将会被移动到survivor区。对象在survivor每经过一次minor gc,年龄就会加一,当达到一定程度,就会被移动到老年代中。
在GC开始时,对象只存在于Eden和叫from的survivor中,to是空的。开始垃圾回收时,把Eden存活的放到to中,from存活的根据年龄决定去向,年龄达到阈值会被移动到老年代,没达到的放到to中。然后Eden和from已经被清空了,from和to再交换角色,现在的to就是下次回收的from,不管怎么样,让to这个区域为空。如果在垃圾回收时to被填满了,剩下的所有对象都会放到老年代里面。
清理方式:
1.minor gc:当新生代的Eden区满了,触发minor gc,清理新生代。
` 2.full/major gc:清理整个堆空间。触发条件:
1)system.gc(),系统建议执行full gc;
2)老年代空间不足;
3)方法区空间不足;
4)通过minor gc 进入老年代的平均大小大于老年代可用内存;
5)由Eden区、From区向To区复制时,对象大小大于To可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小.
分代以后GC roots如何清理?
分代式GC是一种部分收集(partial collection)的做法。在执行部分收集时,从GC堆的非收集部分指向收集部分的引用,也必须作为GC roots的一部分。minor gc只收集新生代里的垃圾,则新生代属于“收集部分”,而老年代属于“非收集部分“,那么从老年代指向新生代的引用也算作GC roots的收集部分,会被GC一并清除。
除了年轻代到老年代的引用之外,有些带有弱引用语义的结构,比如说记录所有当前被加载的类的SystemDictionary、记录字符串常量引用的StringTable等,在minor gc时必须要作为强引用的GC roots,而在收集整堆的full GC时则不会被看作强引用 GC roots。所以,minor GC清理的东西也是很多的!
强引用:普通new对象的引用,如果一个对象具有强引用,GC绝对不会回收它。
弱引用:描述非必需的对象,GC回收时只要扫描到,就会回收它关联的对象。
4. 增量算法
一次性清除所有的垃圾对象比较耗时,用户线程无法进行,影响用户体验。所以增量算法是让用户线程和垃圾回收线程交替执行,直到垃圾回收器回收完毕。
优点: 系统延时更低,用户体验更加流畅友好。
缺点: 线程之间的切换和上下文转换的消耗,整体的垃圾回收成本上升,系统吞吐量下降。
1.串行垃圾回收器Serial Garbage Collector
serial,为单线程环境设计的,也就是说它运行的时候,没有其他线程,所以它的运行效率是最高的。
serial old,在老年代下使用,采用标记-整理算法,同样也是单线程的。
由于是单线程的所以在服务Server模式下已经不再使用,但还是客户Client模式下默认的垃圾回收器。
Java VM虚拟机的工作模式:
Server模式相对Client启动更慢,因为要收集更多的系统性能信息,使用更复杂的优化算法对程序优化。因此当程序完全启动后,执行速度会远远快于Client模式。
2.并行垃圾回收器 parallel garbage collector
ParNew(新生代)
ParNew是serial收集器的新生代多线程实现,在进行垃圾回收时依然会暂停所有用户线程,只是比较serial而言,它会运行多线程回收。它是工作在server模式下的虚拟机首选的新生代收集器,而且它也是能和cms收集器配合工作的收集器。
ParNew在单CPU情况下肯定不如serial,但是随着CPU数量增加,它默认开启的线程数和CPU相同,它对系统的资源利用效率会更好。
Parallel
Parallel Scavenge也是多线程新生代垃圾回收器,和ParNew不同的是它所关注的是吞吐量。吞吐量=运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间),(一般来说,响应时间越快,吞吐量越高,但是随着吞吐量逐渐达到一个限制值之后,响应时间将迅速下降)停顿时间越短越适合与用户交互,提升用户体验,主要适合在后台运算而不需要太多交互任务。
Parallel old是Parallel Scavenge的老年代版本,同样采用多线程,在注重吞吐量和cpu资源敏感的场合可以优先考虑parallel scavenge/old收集器。
3.并行 标记-清除算法 垃圾回收器 Concurrent Mark-Sweep Garbage Collector (老年代)
目前应用广泛,是一种以获取最短停顿时间为目标的垃圾收集器,适合于和用户交互的业务,标记清除Mark Sweep算法实现,收集过程如下:
1.初始标记(initial mark)
2.并发标记(concurrent mark)
3.重新标记(remark)
4.并发清除(concurrent sweep)
初始标记,只是标记一下GC Roots能直接关联到的对象,速度很快;并发标记是把GC Roots所在的图全部遍历一遍,即GC roots tracing过程;重新标记是修正,把并发标记过程发生变化的对象标记修改。
注意初始标记和重新标记还是会stop the world,但是在耗费时间更长的并发标记和并发清除两个阶段都可以和用户进程同时工作。由于CMS是基于标记-清除算法的,所以会导致有 大量的空间碎片 产生,在给较大的对象分配内存时,会出现老年代内存空间还有很大的剩余,却无法找到连续的空间分配当前对象,这时候不得不提前开始一次full GC(整个堆空间进行垃圾回收)。可以通过设置,对清理后进行的内存进行碎片整理,但内存整理是无法并发的,所以停顿时间会变长。而且只能和Serial和ParNew搭配使用。
4.G1垃圾回收器
G1垃圾回收器适用于堆内存很大的情况,是一款面向服务端的收集器。它内存布局和其他的垃圾收集器差异很大,将内存分为多个大小相等的独立区域(因此新生代和老年代不存在物理隔离,G1可以独自管理整个空间),区域之间使用复制算法,但整体采用标记-整理算法,不会产生空间碎片。在分配到较大的对象时,不会因为得不到连续的空间而提前触发full GC,有利于程序的长时间运行。G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
结束的好匆忙,能看到这里,大家肯定也是知识的爱好者,希望可以得到大家独到的见解!
``