2019独角兽企业重金招聘Python工程师标准>>>
垃圾收集器与内存分配策略简介:
一:垃圾收集算法
1.1:简介
说起垃圾收集,大部分人都把这项技术当做java语言的伴生物,其实,GC的历史比java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言,档Lisp在胚胎的时候,人们就在考虑GC需要完成的三件事情:
1:那些内存需要回收
2:什么时候回收
3:如何回收
1.2:垃圾收集算法:
由于笔者也是刚开始学习,对算法的具体实现不甚了解,所以请见谅。
1.2.1标记-清除算法
该算法也是最基础的收集算法,就给名字一样,分为标记和清除两部分,首先标记处所有需要清理的对象,然后在标记完成之后在统一回收。该算法的最大缺点就是标记清除之后会产生大量不连续的内存碎片,空间碎片化太高可能会导致,当程序在以后的运行霍城中需要分配大对象的时候没有办法找到连续的空间再去触发一次垃圾回收动作,有点得不偿失,还有一点就是该算法的收集效率也不是太高。如下所示:
1.2.2:复制算法:
为了解决效率问题,一种称为“复制”算法的收集算法出现了,他讲容量划分为大小相等的两块,每次只是用一块,当一块内存块用完之后,就把存活的对象复制到另一块上去,然后把已经使用过的内存空间一次清理掉,这样使得每次都对其中一块进行垃圾回收,再次进行内存分配的时候也不用考虑空间碎片化的问题。这种算法也有坏处就是内存压缩为原来的一半,利用率比较低,典型的空间换时间的解决方案。复制算法的执行过程如下图哦所示:
而且还有一点就是复制存活对象的时候,必须要存活对象比较少,如果存活对象比较多,那么复制耗费的资源也是比较多的,IBM的研究机构表明,新生代的对象基本都是朝生夕死的,所以并不需要按照1:1的比例划分,而是将新生代内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和一块Survivor空间,回收的时候,将他们中的存活对象复制到另外一块没有用到的Survivor空间中,然后清理Eden和一块Survivor空间。但是也有例外,档Survivor空间不足的时候,需要依赖其他内存(这里指的是老年代)进行分配担保。
1.2.3:标记-整理算法:
复制算法在存活率比较低的时候还好,当对象的存活率比较高的时候,比如在老年代,存活率比较高,对象的复制耗费的资源比较多的情况下就有点得不偿失,所以老年代一般不采用该算法。
根据老年代的特点,有人提出了另外一种“标记-整理”算法,顾名思义就是先标记在整理,标记过程和“标记-清除“算法中的标记一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有的存活对象都向一端移动,然后清理掉边界以外的内存,这样也避免了内存空间碎片化,过程如下图所示:
1.2.4:分代收集算法:
当前的商业虚拟机都采用的是”分代收集“算法,一般是把java堆分成新生代和老生代,这样就可以根据各个年代的特点采用最适当的垃圾收集算法,新生代中,对象大多是”朝生夕死“可以采用复制算法,而老年代的对象存活率比较高,而且没有担保空间进行内存分配,就要采用”标记-清除算法“或者”标记-整理“算法。
1.3:垃圾收集器:
如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
目前HotSpot虚拟机的垃圾收集器主要如下:
sun公司主要是针对新生代和老年代不同特征,采用分代收集算法,其中上图有连线的是说明可以相互搭配使用,
1.3.1:Serial收集器:
他是最基本,历史悠久的垃圾收集器,从名字可以看得出,他是一个单线程的收集器,他的单线程不仅仅是说明她只会使用一个CPU或一条收集线程去完成垃圾回收工作,而是他在进行垃圾回收的时候,必须暂停其他所有的工作线程,简称(Stop The World)直到收集结束,可能说既然这样,那么为什么还要改收集器,俗话说:有利必有弊,他有着优于其他收集器的地方,简单而高效(相对于其他单线程收集器),对于限定单个CPU的坏境来说,Serial收集器由于没有线程交互的开销,专心做垃圾回收自然可以获得最高的单线程垃圾回收效率。
1.3.2:ParNew收集器:
他其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数,收集算法、回收策略都与Serial收集器完全一样。
ParNew收集器除了多线程收集之外,其他与Serial收集器相比没有太多创新之外,但是他确实运行在Server模式下的虚拟机中首选的新生代收集器,还有一个与性能无关但很重要的原因就是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。CMS收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,但是CMS作为老年代的收集器,却无法与jdk1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作,所以在jdk1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中而一个。ParNew收集器也是使用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器,也可以使用-XX:+UseParNewGC选项强制指定它。
ParNew收集器在单CPU的环境中绝对不会比Serial收集器更好的结果。
1.3.3:Parallel Scavenge收集器
该收集器也是一个新生代的垃圾收集器,他也是使用复制算法的收集器,又是一个并行的垃圾收集器。该收集器的特点是他的关注点与其他的收集器不同,CMS等收集器的关注点是尽可能缩短垃圾回收时用户线程的停顿时间,而parallel Scavenge收集器的目标是大道一个可控制的吞吐量。所谓吞吐量就是CPU用于运行用于代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾回收时间),比如虚拟机总共运行100分钟,垃圾回收占用了1分钟,那么吞吐量就是99%。
该收集器提供了两个参数精确控制吞吐量,一个是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis,在一个就是直接设置吞吐量大小的-XX:GCTimeRatio参数。
1.3.4:Serial Old收集器
他是Serial收集器的老年代版本,同样是一个单线程收集器,它采用的是“标记-整理”算法,在client模式下采用,该收集器的主要意义就是第一是在jdk1.5及以前版本中与Parallel Scavenge收集器搭配使用,第二就是作为CMS收集器的备用方案。
1.3.5:Parallel Old收集器
他是Parallel Scavenge收集器的老年代版本,采用的是多线程和”标记-整理“算法,该收集器是jdk1.6之后提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态,原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old收集器之外别无选择,但是Serial Old收集器运行在服务端应用新能上的拖累,即时使用了Parallel Scavenge收集器也未必能在整理应用上获得吞吐量最大化的效果,又因为老年代的Serial Old收集器无法应用多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有Parnew加CMS组合给力。
知道Parallel Old收集器出现之后,”吞吐量优先“收集器终于有了名副其实的应用组合,在注重吞吐量机CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加上Parallel Old收集器。
1.3.6:CMS收集器:
CMS(Concurrent Mark Sweep)收集器是一种获取最短回收停顿时间为目标的收集器,目前很大一部分的java应用都集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,已给应用带来较好的体验,CMS收集器就非常符合这类应用的需求。
从名字上看就可以看出CMS收集器采用的是”标记-清楚“算法,他的运行过程向对于前面的集中收集器来说更复杂一些,主要分为四个步骤:
1:初始标记,2:并发标记,3:重新标记,4:并发清除,其中初始标记和并发标记仍然需要“Stop The World”,初始标记只是标记GC Roots能直接关联到的对象,速度很快,并发表及阶段就是进行GC Roots Tracing的过程,而重新标记是为了修正并发并发标记期间,因用户程序继续运行而导致标记产生变动的一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍微长一些,但远比并发标记的时间段。
CMS是一款很优秀的收集器,他的主要优点是并发收集、低停顿,但是他还有很多缺点
1:对CPU资源比较敏感,2:无法处理浮动垃圾,3:就是CMS是一款基于”标记-清楚“算法,他会产生一些不连续的空间碎片,但是CMS还提供了一个-XX:UseCMSCompactAtFullCollection的参数,用于在享受完Full GC服务只有额外享受一次免费的碎片整理过程,但是内存整理的过程是无法并发的,空间碎片问题没有了,但是停顿时间变长了,虚拟机设计团队还提供了一个额外的参数-XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC会后,跟着来一次带压缩的。
1.3.7:G1收集器
G1收集器是垃圾收集器理论进一步发展的产物,他与前面CMS收集器相比有着明显的改进,一是G1收集器是基于”标记-整理“算法实现的收集器,也就是说他不会产生空间碎片,这对长时间运行的系统来说很重要。二是他可以非常精确的控制停顿,他们让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集的时间不超过N毫秒。
G1收集器可以实现在基本补习生吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力避免全区域的内存垃圾收集,之前的垃圾收集器进行的收集都是整个新生代或老年代,而G1是将整个java堆划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积成都,在后台维护一个优先列表,每次根据允许的垃圾收集时间,优先回收垃圾最多的区域,这也是G1名字的来历,Garbage First,区域划分及优先级的区域回收,保证了G1收集器在优先的时间内可以获得最高的垃圾效率。
1.4:垃圾回收器参数:
UseSerialGC:虚拟机运行在Client模式下的默认值,打开次开关后,使用Serial+Serial Old收集器组合进行内存垃圾回收
UseParNewGC:打开此开关后,使用ParNew+Serial Old收集器进行垃圾回收
UseConMarkSweepGC:打开此开关后,使用ParNew +CMS + Serial Old收集器组合进行内存回收,Serial Old收集器将作为CMS收集器出现在Concurrent ModeFailure失败后的后备收集器使用
UseParallelGC:虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge+Servial Old收集器进行垃圾回收
UseParallelOldGC:打开此开关后,虚拟机采用Parallel Scavenge + Parallel Old收集器进行垃圾收集
SurvivorRatio:新生代中Eden区域与Survivor区域的容量值比,一般是8,代表的比例关系为8:1
PretenureSizeThreshold:直接晋升老年代对象的大小,设置这个参数后,对象大于该参数的直接进入老年代
MaxTenuringThreshold:晋升老年代的对象年龄,每个对象在坚持过一次MinorGC之后,年龄加1,超过这个参数直接进入老年代
UseAdaptiveSizePolicy:动态调整java堆中各个区域的大小以及进入老年代的年龄
HandlePromotingFailure:是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象都存活的极端情况
ParallelGCThreads:设置并行GC时内存回收的线程数量
GCTimeRation:GC时间中总时间的比例,默认值为99,即允许1%的GC时间,尽在Parallel Scavenge收集器时生效
MaxGCPauseMills:设置GC的最大停顿时间,尽在使用Parallel Scavenge收集器时生效
CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发垃圾收集,默认值为68%,当且仅在使用CMS收集器时生效
UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否进行一次内存碎片整理,当且仅在使用CMS收集器时生效
CMSFullGCsBeforeCompaction:设置CMS收集器在进行若干次垃圾收集后自动启用一次碎片整理,当且仅在使用CMS收集器时生效。