您的位置:首页 > 编程语言 > Java开发

JAVA 的GC算法和垃圾收集器

2019-07-21 02:20 1626 查看

1.标记清除算法 

黑色部分代表可回收对象,灰色部分代表存活对象,绿色部分代表未使用的。
最基础的收集算法就是标记清除算法如同他名字一样,算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收的对象,在标记完成后同一回收所有被标记的对象,
标记的过程就是我们之前讲过的可达性分析算法。当需要回收时,我们把黑色标记的部分进行回收。
标记算法优缺点:
优点:简单直接易懂
缺点:
1、空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集动作。
例如每个格子都是1K,要分配5k怎么办?只能提前进行垃圾收集了。
2、效率问题:标记和清除两个过程效率都不高。

 

2.复制算法

 

为了解决效率的问题,"复制算法"应用而生,复制算法过程:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,
就将还存活者的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是针对整个半区进行内存回收,
内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。
优缺点:
优点:实现简单,运行高效。
缺点:1、这种算法的代价时将内存缩小了原来的一半,代价内存利用率不高。
2、在对象存活率较高时就要进行较多的复制操作,效率将会降低

 

 

 3、标记整理算法

 

 

为了解决复制算法的这些缺点,提出了标记整理算法。标记整理算法过程:标记过程仍然与"标记—清除"算法一样,

但后续的步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

 

4、分代收集算法
每种算法都有优缺点:那GC采用什么算法?把算法都用上,集百家之所长。为什么JVM的GC要分代收集?因为对象存过周期不同,IBM研究过,百分之90以上的对象都是"朝生夕死",生存周期很短,不到10%需要长期存活,按照这样的对象存活时间就可以分为两个区:新生代和老年代。老年代是指存活超过一定时间的,存活时间指对象活过多少次(默认为15次)GC,就成为老年代。
基本上我们所有new出的所有对象都是在新生代进行分配。新生代又分为不同的区段:Eden区(伊甸园)和Survivor区(幸存者)。新生代采用的算法是复制算法,因为百分之90以上的对象都是"朝生夕死",所以大部分对象真正存活的对象不多,需要移动的对象也不多,同时复制算法内存整理后内存非常规整。

 

新生代分为1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,因为一般情况下新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold 来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

 

举个例子,假如我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我15岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),

然后被回收。


新生代的垃圾回收叫minor gc,老年代的gc叫做 major gc,整个堆的gc叫做full gc。
老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用"标记清除"或者"标记整理" 算法来进行回收。

 

新生代垃圾收集器:

 

 

从图中可以看出新生代的垃圾收集器有三种,都是采用的复制算法。 

Stop-The-World:Java中一种全局暂停的现象,全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互多半由于GC引起 

 

Serial 新生代的单线程垃圾回收,采用复制算法,进行垃圾回收时,工作线程必须停止, 会产生Stop the world 现象。

ParNew垃圾收集器是Serial,收集器的多线程版本,搭配CMS垃圾回收器的首选

ParallelScavenge类似ParNew,更加关注吞吐量,达到一个可控制的吞吐量;本身是Server级别多CPU机器上的默认GC方式,主要适合后台运算不需要太多交互的任务;

 

老年代的垃圾收集器:

 

 

SerialOld:jdk7/8默认的老生代垃圾回收器Client模式下虚拟机使用

ParallelOld:ParallelScavenge收集器的老年代版本,为了配合Parallel Scavenge的面向吞吐量的特性而开发的对应组合;在注重吞吐量以及CPU资源敏感的场合采用

CMS:尽可能的缩短垃圾收集时用户线程停止时间;缺点在于:1.内存碎片2.需要更多cpu资源3.浮动垃圾问题,需要更大的堆空间
重视服务的响应速度、系统停顿时间和用户体验的互联网网站或者B/S系统。互联网后端目前cms是主流的垃圾回收器;

G1:JDK1.7才正式引入,采用分区回收的思维,基本不牺牲吞吐量的前提下完成低停顿的内存回收;可预测的停顿是其最大的优势;
面向服务端应用的垃圾回收器,目标为取代CMS

 

新生代收集器有3种,老年代收集器有三种,那么是不是任意搭配就可以?其实是不可以的那么怎么搭配使用呢?其实只有在分代收集示意图中连线了的才可以搭配一起使用。

 

 

新生代收集器有3种,老年代收集器有三种,那么是不是任意搭配就可以?其实是不可以的那么怎么搭配使用呢?其实只有在分代收集示意图中连线了的才可以搭配一起使用。

 

 那么如何查看本机的垃圾回收器是哪种收集器?打开命令窗口输入命令:java -XX:+PrintCommandLineFlags -version,运行会得到如下图的结果:

 

 

-XX:+UseParallelGC就指明了新生代使用ParallerGC收集器(Parallel Scavenge也叫Paraller),老年代使用Serial Old收集器。
-XX:+UseSerialGC :Serial/Serial Old收集器这样组合,新生代和老年代都用串行收集器。
-XX:+UseParallerOldGC:新生代使用Parallel Scavenge,老年代使用Parallel Old
-XX:+UseConcMarkSweepGC ,表示新生代使用ParNew,老年代的用CMS。

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: