Java堆从GC的角度能够细分为:新生代(Eden区、From Survivor区和To Survivor区)和老年代。java
新生代是用来存放新生的对象。通常占据堆的1/3空间。因为频繁建立对象,因此新生代会频繁触发MinorGC进行垃圾回收。新生代又分为Eden区、ServivorFrom、ServivorTo三个区。 1.1.1.Eden区Java新对象的出生地(若是新建立的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。 1.1.2.ServivorFrom上一次GC的幸存者,做为这一次GC的被扫描者。 1.1.3.ServivorTo保留了一次MinorGC过程当中的幸存者。 1.1.4.MinorGC的过程(复制->清空->互换)MinorGC采用复制算法。 1:eden、servicorFrom 复制到ServicorTo,年龄+1首先,把Eden和ServivorFrom区域中存活的对象复制到ServicorTo区域(若是有对象的年龄以及达到了老年的标准,则赋值到老年代区), 同时把这些对象的年龄+1(若是ServicorTo不够位置了就放到老年区); 2:清空eden、servicorFrom而后,清空Eden和ServicorFrom中的对象; 3:ServicorTo和ServicorFrom互换最后,ServicorTo和ServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区。
老年代主要存放应用程序中生命周期长的内存对象。老年代的对象比较稳定,因此MajorGC不会频繁执行。在进行MajorGC前通常都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,
致使空间不够用时才触发。当没法找到足够大的连续空间分配给新建立的较大对象时也会提早触发一次MajorGC进行垃圾回收腾出空间。MajorGC采用标记清除算法:首先扫描一次全部老年代,标记出存活的对象,
而后回收没有标记的对象。MajorGC的耗时比较长,由于要扫描再回收。MajorGC会产生内存碎片,为了减小内存损耗,咱们通常须要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,
就会抛出OOM(Out of Memory)异常。
不定时去堆内存中清理不可达对象。不可达的对象并不会立刻就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,即便程序员能明确地判断出有一块内存已经无用了,是应该回收的,
程序员也不能强制垃圾收集器回收该内存块。程序员惟一能作的就是经过调用System.gc 方法来"建议"执行垃圾收集器,但其是否能够执行,何时执行却都是不可知的。
这也是垃圾收集器的最主要的缺点。固然相对于它给程序员带来的巨大方便性而言,这个缺点是瑕不掩瑜的。
根搜索算法的基本思路就是经过一系列名为”GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,
则证实此对象是不可用的。 这个算法的基本思想是经过一系列称为“GC Roots”的对象做为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,
则证实此对象是不可用的。 那么问题又来了,如何选取GCRoots对象呢?在Java语言中,能够做为GCRoots的对象包括下面几种: (1). 虚拟机栈(栈帧中的局部变量区,也叫作局部变量表)中引用的对象。 (2). 方法区中的类静态属性引用的对象。 (3). 方法区中常量引用的对象。 (4). 本地方法栈中JNI(Native方法)引用的对象。
最基础的垃圾回收算法,分为两个阶段,标注和清除。程序员
标记阶段标记出全部须要回收的对象,清除阶段回收被标记的对象所占用的空间。该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。
标记清除算法的优势和缺点 1. 优势 - 是能够解决循环引用的问题 - 必要时才回收(内存不足时) 2. 缺点: - 回收时,应用须要挂起,也就是stop the world。 - 标记和清除的效率不高,尤为是要扫描的对象比较多的时候 - 会形成内存碎片(会致使明明有内存空间,可是因为不连续,申请稍微大一些的对象没法作到)
为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。算法
概念 若是jvm使用了coping算法,一开始就会将可用内存分为两块,from域和to域, 每次只是使用from域,to域则空闲着。当from域内存不够了,开始执行GC操做,这个时候,会把from域存活的对象拷贝到to域,
而后直接把from域进行内存清理。 应用场景 coping算法通常是使用在新生代中,由于新生代中的对象通常都是朝生夕死的,存活对象的数量并很少,这样使用coping算法进行拷贝时效率比较高。jvm将Heap 内存划分为新生代与老年代,
又将新生代划分为Eden(伊甸园) 与2块Survivor Space(幸存者区) ,而后在Eden –>Survivor Space 以及From Survivor Space 与To Survivor Space 之间实行Copying 算法。
不过jvm在应用coping算法时,并非把内存按照1:1来划分的,这样太浪费内存空间了。通常的jvm都是8:1。也便是说,Eden区:From区:To区域的比例是始终有90%的空间是能够用来建立对象的,
而剩下的10%用来存放回收后存活的对象。 1、当Eden区满的时候,会触发第一次young gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发young gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,通过此次回收后还存活的对象,
则直接复制到To区域,并将Eden和From区域清空。 2、当后续Eden又发生young gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。 3、可见部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终若是仍是存活,就存入到老年代 注意: 万一存活对象数量比较多,那么To域的内存可能不够存放,这个时候会借助老年代的空间。
优缺点 优势:在存活对象很少的状况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中致使的引用更新问题。 缺点: 会形成一部分的内存浪费。不过能够根据实际状况,将内存块大小比例适当调整;若是存活对象的数量比较大,coping的性能会变得不好。
标记清除算法和标记压缩算法很是相同,可是标记压缩算法在标记清除算法之上解决内存碎片化问题安全
压缩算法简单介绍
任意顺序 : 即不考虑原先对象的排列顺序,也不考虑对象之间的引用关系,随意移动对象;
线性顺序 : 考虑对象的引用关系,例如a对象引用了b对象,则尽量将a和b移动到一块;
滑动顺序 : 按照对象原来在堆中的顺序滑动到堆的一端。
优缺点
优势:解决内存碎片问题,缺点压缩阶段,因为移动了可用对象,须要去更新引用。
这种算法,根据对象的存活周期的不一样将内存划分红几块,新生代和老年代,这样就能够根据各个年代的特色采用最适当的收集算法。能够用抓重点的思路来理解这个算法。服务器
新生代对象朝生夕死,对象数量多,只要重点扫描这个区域,那么就能够大大提升垃圾收集的效率。另外老年代对象存储久,无需常常扫描老年代,避免扫描致使的开销。多线程
新生代 在新生代,每次垃圾收集器都发现有大批对象死去,只有少许存活,采用复制算法,只须要付出少许存活对象的复制成本就能够完成收集; 老年代 而老年代中由于对象存活率高、没有额外空间对它进行分配担保,就必须“标记-清除-压缩”算法进行回收。新建立的对象被分配在新生代,若是对象通过几回回收后仍然存活,那么就把这个对象划分到老年代。 老年代区存放Young区Survivor满后触发minor GC后仍然存活的对象,当Eden区满后会将存活的对象放入Survivor区域,若是Survivor区存不下这些对象,GC收集器就会将这些对象直接存放到Old区中,
若是Survivor区中的对象足够老,也直接存放到Old区中。若是Old区满了,将会触发Full GC回收整个堆内存。
Java堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法,所以java虚拟中针对新生代和年老代分别提供了多种不一样的垃圾收集器。并发
Serial收集器是发展最悠久的垃圾收集器。在jdk1.3的时候只能用咱们serial垃圾回收器。他是一个单线程的垃圾回收器。用在咱们的新生代复制算法在桌面应用比较多(单线程服务器上,堆内存比较小的应用使用效率比较高).当咱们gc执行时候会暂停咱们的全部的线程这个步骤简称STW (Stop The World)jvm
ParNew垃圾收集器实际上是Serial收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集以外,其他的行为和Serial收集器彻底同样,ParNew垃圾收集器在垃圾收集过程当中一样也要暂停全部其余的工做线程。性能
Parallel Scavenge收集器也是一个新生代垃圾收集器,一样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU用于运行用户代码的时间/CPU总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量能够最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适用于在后台运算而不须要太多交互的任务。自适应调节策略也是ParallelScavenge收集器与ParNew收集器的一个重要区别spa
-XX:MaxGCPauseMillis 垃圾回收器最大停顿时间
-XX:GCTimeRatio 吞吐量大小 (0,100) 默认最大99
Serial Old是Serial垃圾收集器年老代版本,它一样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的年老代垃圾收集器。
Parallel Old收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在JDK1.6才开始提供。在JDK1.6以前,新生代使用ParallelScavenge收集器只能搭配年老代的Serial Old收集器,只能保证新生代的吞吐量优先,没法保证总体的吞吐量,Parallel Old正是为了在年老代一样提供吞吐量优先的垃圾收集器,若是系统对吞吐量要求比较高,能够优先考虑新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略。
Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其余年老代使用标记-整理算法不一样,它使用多线程的标记-清除算法。
最短的垃圾收集停顿时间能够为交互比较高的程序提升用户体验。CMS工做机制相比其余的垃圾收集器来讲更复杂,整个过程分为如下4个阶段:
1.初始标记只是标记一下GC Roots能直接关联的对象,速度很快,仍然须要暂停全部的工做线程。 2.并发标记进行GC Roots跟踪的过程,和用户线程一块儿工做,不须要暂停工做线程。
3.从新标记为了修正在并发标记期间,因用户程序继续运行而致使标记产生变更的那一部分对象的标记记录,仍然须要暂停全部的工做线程。
4.并发清除清除GC Roots不可达对象,和用户线程一块儿工做,不须要暂停工做线程。因为耗时最长的并发标记和并发清除过程当中,垃圾收集线程能够和用户如今一块儿并发工做,
因此整体上来看CMS收集器的内存回收和用户线程是一块儿并发地执行。
优势:并发收集,低停顿
缺点:占用大量的cpu资源,没法处理浮动垃圾,会产生碎片化
Garbage first垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与CMS收集器,G1收集器两个最突出的改进是:
1.基于标记-整理算法,不产生内存碎片。
2.能够很是精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
G1收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,而且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所容许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保G1收集器能够在有限时间得到最高的垃圾收集效率。
有对应的参数分配多大的对象会直接进入咱们的老年代 使用 要指定的收集器才有效
-XX:PretenureSizeThreshold
当对象生成在EDEN区失败时,出发一次YGC,先扫描EDEN区中的存活对象,进入S0区,S0放不下的进入OLD区,再扫描S1区,若存活次数超过阀值则进入OLD区,其它进入S0区,而后S0和S1交换一次。
那么当发生YGC时,JVM会首先检查老年代最大的可用连续空间是否大于新生代全部对象的总和,若是大于,那么此次YGC是安全的,若是不大于的话,JVM就须要判断HandlePromotionFailure是否容许空间分配担保。
容许分配担保:
JVM继续检查老年代最大的可用连续空间是否大于历次晋升到老年代的对象的平均大小,若是大于,则正常进行一次YGC,尽管有风险(由于判断的是平均大小,有可能此次的晋升对象比平均值大不少);
若是小于,或者HandlePromotionFailure设置不容许空间分配担保,这时要进行一次FGC。
新生代采用的是复制收集算法,S0和S1始终只是用其中一块内存区,当出现YGC后大部分对象仍然存活的话,就须要老年代进行分配担保,把survior区没法容纳的对象直接晋升到老年代。
那么这种空间分配担保的前提是老年代还有容纳的空间,一共有多少对象会活下来,在实际完成内存回收以前是没法明确知道的,因此只好取以前每次回收晋升到老年代对象容量的平均值大小做为经验值,与老年代的剩余空间比较,决定是否进行FGC来让老年代腾出更多空间。
当 Survivor 空间中相同年龄全部对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就能够直接进入老年代,而不须要达到默认的分代年龄