⑴ JVMGC及内存分配策略
GC就是垃圾回收,它的主要作用就是回收程序中不再使用的内存.
JVM执行GC内存回收的时候如何判断内存是否可以回收呢,就是看对象是否还存活,如果不存活则回收对象对一个的内存空间
那判断对象是否存活有哪些方法呢?
引用计数法顾名思义就是通过计算对象被引用的次数来判断对象是否还存活。
例如下面的代码片段
这种情况下可以理解为o p都是堆内存中的对象,另外p对象的 name 被 o 引用(这里 name字段也是一个对象)。如下
那么此时, o的引用计数为 1 , p的引用计数为2。也就是说引用计数法判断这个引用值为0的时候就会来回收这个对象所占用的内存。
这种方式的优缺点是什么呢
优点
缺点
java判断对象是否存活的依据就是可达性分析。
JVM根据一些GC Roots来进行可达性判断,若从某一个或某几个GC Root可以访问到这个对象那么这个对象就不可回收。即从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
那么GC Roots有哪些呢
一般我们创建对象的操作都是使用的强引用
一些有用但是并非必需,用软引用关联的对象,系统将要发生OOM之前,这些对象就会被回收。
虚拟机参数:
一些有用(程度比软引用更低)但是并非必需,用弱引用关联的对象,只能生存到下一次垃圾回收之前,GC发生时,不管内存够不够,都会被回收。
幽灵引用,最弱,被垃圾回收的时候收到一个通知
这种处理方式但是容易出现内存的碎片导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可,实现简单,运行高效。这种算法的代价是将内存缩小为了原来的一半,造成内存的浪费。
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor[1]。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
最古老的,单线程,独占式,成熟,适合单CPU 服务器
和Serial基本没区别,唯一的区别:多线程,多CPU的,停顿时间比Serial少
获取最短回收停顿时间为目标的收集器。
目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
从名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:
浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”。同时用户的线程还在运行,需要给用户线程留下运行的内存空间。
特点:
G1当出现内存不足的的情况,也可能进行的FullGC回收。
GC收集器和我们GC调优的目标就是尽可能的减少Stop Thr World 的时间和次数。
ZGC通过技术手段把stw的情况控制在仅有一次,就是第一次的初始标记才会发生,这样也就不难理解为什么GC停顿时间不随着堆增大而上升了,再大我也是通过并发的时间去回收了
关键技术(这部分以后在找相关的知识补充)
⑵ 【JVM】对象分配与回收--垃圾回收机制
对象回收需要确认三件事,那些需要回收(对象存活判定,二次标记),何时回收(GC触发条件)以及如何回收(垃圾回收算法,垃圾回收器)
1)引用计数法
2)可达性分析:GCRoots作为起始点,沿着引用链搜索。
GCRoots可以为:虚拟机栈中引用的对象;本地方法栈中native引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;
1)强引用:即常见的那种引用,一个引用指向堆内存中对象。
2)软引用:还有用但非必须的,当空间不足了(即将OOM),才会对它进行回收。
3)弱引用:也是非必须的,不管空间剩余多少,只要有垃圾回收就进行回收。
4)虚引用:一个对象是否有虚引用,不会对它的生存事件有任何影响,也无法通过虚引用获得一个实例。唯一目的能在对象被回收时收到一个系统通知。
1)当对象被判定为不可达后,会进行一次标记,并筛选出覆盖了finalize方法且还没被执行过的对象进入下一步,那些没有覆盖的,或覆盖但已执行过的(finalize只能执行一次)将会被回收。
2)将筛选出的对象加入一个队列,并有一个优先级很低的Finalier线程去执行队列中对象的finalize方法,若finalize方法中该对象重新获得了引用,则复活,否则在第二次标记时他将被回收(第二次标记就是第一步中的标记,循环这两步)。
1)标记清除:将不可达的对象进行标记,GC时回收、---->效率低、空间碎片
2)复制:将内存区域(堆)分为两块,每次只使用其中的一块。将可达对象进行标记,复制到另一边,然后将刚才那一边全部清空。
这中算法适用于存活对象很少的情况,经常被用于新生代的垃圾回收。
3)标记整理:将不可达对象标记后,清除对象后将存活对象向一端移动,减少空间碎片的产生。
该算法被广泛使用于老年代中。
4)分代收集:由于不同的对象有自己不同的特点。将区域划分为新生代与老年代。
新生代:对象大都朝生夕死,存活时间短。每次GC只有少量对象存活。一般用 复制算法 。(复制算法一般需要空间分配担保空间)
老年代:对象存活时间较长。也没有额外空间对他进行分配担保,更适合标记清除 、标记整理算法。
要注意垃圾回收器的设计目标是,有的是为了减少STW的时间,有的是为了吞吐量。这也应该作为一个选择回收器的标准。
1)Serial / Serial Old
Serial是一个单线程的垃圾回收器,进行垃圾回收时,必然要STW。Serial在新生代采用复制算法,其对应的老年代回收器在老年代采用标记整理法。
2)ParNew
ParNew实质上一个多线程的Serial,他能够实现多个线程进行垃圾回收,但仍是需要用户线程STW之后才能并发执行垃圾回收。一般开启CPU核数个线程,用-XX:ParallelGCThreads设置。也可以用-XX:SurvivorRatio设置年轻代Eden与Survivor的比例,-XX:HandlerPromotionFailure设置空间分配担保是否开启。
是一种年轻代的垃圾回收器,采用复制算法。
3)Parallel Scavenge收集器 /Parallel Old
这是一款新生代收集器,也是采用复制算法。特点是关注点不在于停顿时间,而在于吞吐量。可通过时设置-XX:UseAdapatorSizePolicy为true将该收集器设为一个自适应调节策略,会自动根据吞吐量与停顿时间等因素进行自适应调节。
相关参数:设置吞吐量大小-XX: GCTimeRatio,-XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间,-XX:UseAdaptativeSizePolicy自适应的设置新生代中区域的比例
Parallel Old是它的老年代的回收器,采用多线程和标记整理算法实现,可以选择Parallel Scavenge与Parallel Old配合使用组合为一个注重吞吐量的垃圾回收机制。
4)CMS (Concurrent Mark Sweep)老年代回收器!
由名字就可以看出来,是一款基于并发的标记清除算法的收集器,CMS收集器是针对于老年带的收集器,以最短停顿时间为目标。
STEP 分为四个步骤,其中耗时最长的并发标记与并发清除是与用户线程并发完成的,而其它两个步骤会有短暂的停顿,以此种方式尽量减少停顿时间
a.初始标记:就是标记一些GCRoots的直接引用,速度非常快,这一过程会有短暂的SWT
b.并发标记:与用户线程并发的进行GCRoots tracing,沿引用链去标记不可达的对象。
c.重新标记:由于第二步是与用户线程一起发生的,有可能在回收过程中会有新的垃圾产生,这一步就是对于这些浮动垃圾进行标记(短暂停顿)
d.并发清除:与用户线程并发进行垃圾回收,采用标记清除算法。
优缺点
并发收集、低停顿
a.对CPU资源敏感,降低吞吐量(吞吐量指用户线程占用时间/总运行时间,这种算法占用了用户现成的一部分CPU时间)(并发涉及的程序都会争抢CPU资源)
b.标记清除算法导致的空间碎片:可能会造成大量空间浪费,若大对象(直接进入老年代)存储时,则会由于连续空间不足引发FullGC。可通过设置-XX:UseCMSCompactAtFullCollection开关,设置是否在FullGC前执行碎片整理合并。
c.浮动垃圾:在并发阶段,用户也可能会随时产生垃圾。在这里需要考虑另一个问题,由于是并发的,所以我们需要考虑用户线程需要占用一部分存储空间,就不可能等到老年代快满了再回收垃圾,需预留给用户线程一些空间。可以通过设置-XX:来设置触发FullGC时内存空间使用比例,若这个参数过低,则会增加发生GC的次数;若过高,留给用户空间的内存不够,又会引发Concurrent Mode Failure,而启用Serial Old收集器作为备份方案。
5)G1(Garbage first)
特点
a.将新生代与年老代同一划分为多个Region区域。
b.标记整理算法。
c.与用户线程并发执行。
d.可预测停顿时间。因为可以有计划的避免在整个java堆中进行全局的回收,将堆划分为多个Region,并为每个计算Region里垃圾堆积的价值大小(回收空间大小以及时间),根据价值维护一个Region的优先列表,每次都选取列表中第一个进行回收,即回收价值最大的那个Region。
需要考虑的问题是:化整为零后,多个region之间的一些引用如何确定呢(新生代老年代也有该问题),虚拟机采用Remember Set来避免全表扫描。每个region维护一个Remember Set,在对引用类型进行写操作时,会发生写中断,此时检查该引用的对象是否在别的Region中,若是,则将该信息写入他自己所属Region的RememberSet中。以便在对某一个Region进行可达性分析是不需要全表扫描。
STEP
a.初始标记:仅标记GCRoot是直接引用
b.并发标记:并发的标记所有不在引用链上的引用
c.最终标记:在并发标记阶段新产生的对象会写入Remember set log中,在该阶段将Remember Set log中的数据更新进Remember set中,进行标记。
d.筛选回收:!!!不是并发的,对Region的价值进行排序,并根据用户希望的GC停顿时间制定回收计划,由于只回收一个Region,速度也很快,就没有采取并发。
从前面已经知道了,我们根绝对象生存时间的长短不一这一特征,将堆划分为年轻代和年老代。这里进一步了解年轻代内部划分,以及分配对象时区域之间是如何协助的,从而进一步能够知道GC触发的时间。
1)年轻代的划分与基本工作机制
年轻代一般采用复制算法,将年轻代划分为Eden区与FromSurvivor、ToSurvivor。分配对象时,首先分配到Eden与FromSurvivor区中,然后将可达的对象复制到ToSurvivor区中,注意此时将这些对象的年龄+1,并清除Eden+FromS中的无用对象。然后再将FromS变为ToS,ToS变为FromS,(也省去了将To中的对象复制给From中这一步骤)以便下一次的对象回收。
2)对象分配的机制
a. 一般对象首先分配给Eden区,若空间不足则会触发MinorGC。
b. 大对象会直接分配到年老代,若空间不足触发FullGC。
c. 对象在几个情况下将从年轻代进入年老代:
c1:对象年龄到了(默认为15,在复制过程中年龄增加,因为可达而熬过了一次GC嘛)可通过-XX:MaxTenuringThreshole设置进入年老代的年龄。
c2:动态对象年龄判断:当年轻代中同一年龄的对象所占存储空间之和大于 Survivor 的一半时(占满了toSurvivor?),将大于等于该年龄的对象都放入年老代
c3:空间分配担保:年轻代采用复制算法,就需要有空间在survivor区装不下存活对象的时候帮忙存储多出来的对象(一般发生于一次MinorGC之前,存活对象过多导致survivor放不下了),年老代即为空间分配担保的空间。发生空间分配担保时,会进入老年代。
3)空间分配担保机制
年老代进行空间分配担保是有风险的,若担保过来的对象超过了剩余空间,那么年老代会发生FullGC。
所以在发生MinorGC之前,虚拟机会判断新生代所有对象总空间(!!一次保障性的对比,若成立则其子集肯定成立)是否小于年老代最大连续空间大小,若小于,则担保成功,认为这次MinorGC是安全的,执行GC。若大于,则要判断年老代是否开启HandlePromotionFailure(是否允许空间分配担保),若没开启,则直接执行FullGC。若开启了,则判断以往平均担保大小是否小于最大连续空间大小,若小于,则尝试MinorGC(有风险,失败了再FullGC,多一次MinorGC),若大于,则FullGC(包括MinorGC)。
4)总结一下GC触发条件与设置
MinGC:当Eden区不够时,可设置-XX:SurvivorRatio设置年轻代中Eden的比例,-XX:NewRatio设置堆中年轻代比例,-Xms -Xmx设置堆最小最大值。
FullGC:a.System.gc()
b.永久代,类、静态变量,常量太多,不够放 -XX:MaxPermSize
c.老年代:c1大对象直接进入时不够 c2空间分配担保,MinorGC前,不愿意担保直接FullGC,愿意担保且空间小于之前担保平均值FullGC,若大于但担保失败了(这次太多了)先MinorGC(发现不行)再FullGC。
d,CMS回收器:只针对老年代收集,浮动垃圾过多,当超过设置的比例大小时(因为用户线程也有空间),会FullGC。设置老年代空间使用比例
-XX:,到达这么多时FullGC
目前了解的调优方式:
1)频繁GC时,要使用JDK一些工具排查问题具体位置。
jstat:虚拟机各方面的运行数据 jmap:生成内存转储快照 一般通过这两个就能定位堆中的问题,判断是空间设置不合理还是程序问题。
跟着GC出发点走,调整各个区域大小-Xms -Xmx -XNewRatio -XX:SurvivorRatio -XX:permSize,以及年老代相关的 maxTenuringTreshold,handlePromotionFailure(一般设为true,以减少FullGC,给MinorGC机会嘛)
2)性能问题,要根据需求选择合适的垃圾回收器,以停顿时间为目标还是以吞吐量为目标
⑶ 从垃圾回收GCDetails看JVM GC原理
1.启动参数
2.回收原理
为了更好的理解GCDetails信息回忆下新生代回收的算法(图出自网友),此处不会对回收算法进行详细的讲解,也不会介绍ParallelGC的XX:MaxGCPauseMilli、XX:GCTimeRatio等参数。
3.GCDetails信息
4.分析
Parallel Scavenge是JDK8默认的新生代垃圾回收算法,是一种以吞吐量为目标的垃圾回收器,基于标记复制算法,内存分布为堆内存中划分了2块区域,一块为新生代,一块为老年代,默认配置新生代占堆内存1/3,老年代占堆内存2/3,新生代分为Eden,Survivor_To,Survivor_From,默认分配比例为8:1:1,Survivor区负责存储垃圾回收未能回收的对象和晋升到老年代的操作,从上面的GCDetails可以分析一下垃圾回收的大概原理。
以第一行为例,131584K为新生代回收前占用内存大小,21503K为新生代回收后占用内存大小,可以得知这一次GC回收了约110M,垃圾回收器使用Parallel Scavenge,131584K为整个堆内存回收前内存占用大小,42320K为整个堆内存这次GC后内存占用的大小,可以得知这一次GC回收了约89M内存,垃圾回收器使用Parallel Old,花费了大约8.3毫秒,那新生代回收内存与整个堆回收的内存大小差就是新生代晋升到老年代的内存大小,可以得知为21M。
以第二次GC为例,可以看到他触发了Full GC,原因很简单,就是老年代的空间不足,在这次GC中,新生代回收了16269K内存空间,OK代表新生代全部内存都已经回收,老年代回收了316390K-236940K内存空间,大约80M,整个堆内存回收了333077K-271355K内存空间,大约61M,可以得知新生代晋升80-61内存空间,约19M。
在过往的生产经验中只要JVM或者程序没有相关的告警,其实很少去调整启动参数,通过GC日志的学习与分析,我们发现堆内存的大小与系统的吞吐量还是有直接的关系,大家可以尝试使用不同的-Xms、-Xmx1参数来测试系统的吞吐量,我们会发现如果内存过小,会频繁的发生年轻代GC甚至会频发的一直发生Full GC或内存泄漏,其实内存离谱的大也会影响性能,老年代Full GC时STW(stop the word)的时间会比较久,所以一个合适的启动参数是在性能测试中必不可少的一环。
5.垃圾回收器选择参数
在新生代都是Parallel Scavenge回收器的时候,本来想测试一下配合不同的老年代算法来看一下性能,准备使用XX:+UseParallelGC、XX:+UseParallelOldGC,但是发现老年代回收算法一直都是ParOldGen,困惑了一段时间后通过搜索资料发现在JDK 7u4以后的7和JDK8,新生代算法为Parallel Scavenge,老年代默认都为Parallel Old。
6.一点疑问
上面的GC日志中,其中有两个括号,如第一行的(153088K)和(502784K)分别代表新生代分配的内存大小与堆内存分配的大小,但是我们发现这个大小并不是固定的,在七次乃至后面GC的时候括号内的大小发生了改变,这是为什么呢,其实JDK8 Parallel Scavenge默认开启了-XX:+ UseAdaptiveSizePolicy 参数,这个参数会根据吞吐量与垃圾回收的时间动态的调整各内存区域的大小,虽然有默认新生代的分配比例SurvivorRatio=8,但是也不会固定大小,如果在启动参数显示的写SurvivorRatio=8,则分配的内存不会动态调整,大家可以动手实验一下。
在JD8以前新生代垃圾回收除了Parallel Scavenge还有Serial和ParNew大家可以通过以下的指令进行测试,都基于标记复制算法,GC日志在新生代部分都是相同的分析方法。
1.启动参数
2.回收原理
CMS作为一款老年代垃圾回收期,以减少垃圾回收的停顿时间为目标,采用标记清除算法,只能与新生代垃圾回收ParNew和Serial搭配使用,JDK9后只支持与ParNew配合使用,同样本部分不做详细的原理的说明,主要是分析GCDetails日志来加深理解,这部分有两个比较重要的概念,一个是三色标记法与读写屏障,一个是卡表结构,没有了解的可以参考 这篇文章 ,感觉写的比较好理解。
3.GCDetails日志
4.分析
GC (Allocation Failure)是使用ParNew作为新生代的垃圾回收算法,此部分的内存情况与之前分析Parallel相似,所以这部分主要分析CMS老年代的垃圾回收原理。CMS垃圾回收日志其实与垃圾回收的阶段强相关,主要也是通过看日志来反向加强垃圾回收的流程与原理。
1)初始标记-CMS Initial Mark
CMS为了减少停顿时间大家可以记两段标记阶段都STW,剩下的阶段都并行,日志中时间0.328行可以得知一些信息,初始标记阶段老年代使用内存大小为203782K,整个老年代分配大小为349568K,整个堆内存使用内存为221541K,整个堆内存分配的大小为506816K,标记的时间也很短,从这里也能大概算出新生代分配的内存大小约为506816K-349568K=157M左右,与第一行的新生代内存分配大小一致。
2)并发标记-CMS-concurrent-mark,并发标记主要是根据初始标记来进行对象的可达性,日志中时间0.330行得知并行标记花费了0.01s;预清理-CMS-concurrent-preclean,预清理主要解决在并发标记的过程中业务线程对对象引用关系的修改(包括跨代引用和引用改变),主要是扫描标记为“脏块”的卡表,花费了0.01s;
3)可终止预清理-CMS-concurrent-abortable-preclean,该步骤不是一定执行,如果现在新生代内存小于则不执行,如果大于该值继续标记(一直重复)的时候会有退出条件如循环次数、时间阈值、Eden区内存使用率等,在循环退出之前会进行一次YGC减轻后面最终标记的压力(因为老年代存在指向年轻代的指针),通过日志我们可以看出在0.331的可终止预清理阶段进行了三次YGC,从内存的情况上看 有大量对象从新生代晋升到老年代。
小疑问:在该部分我们观察时间指标是0.003/0.136 secs,0.003s是CPU执行的时间,0.136s是可终止预清理的总耗时,他们之间存在一个差值,这部分我其实也没有一个明确的答案,我第一感觉可能是在等待一次新生代GC。
3)最终标记-CMS Final Remark
从日志中时间0.468可以分析最终标记的结果,17759K新生代使用内存的大小,Rescan (parallel)为最后的标记SWT的时间,weak refs processing为清理弱引用的时间,class unloading为卸载未使用类的时间,scrub symbol table为清理符号表,我理解就是清理符号引用,scrub string table为清理字符表,我理解就是清理字面量,最终CMS的最终标记后老年代使用内存为334051K,堆内存使用为351810K。
4)并发清理-CMS-concurrent-sweep,很好理解就是并发清理;并发重置-CMS-concurrent-reset,重新调整CMS的内存结构,以便下次垃圾回收。
5.调优
在CMS中不是并发的垃圾回收一定成功,可能会发生失败,这样就会降级为Serial Old垃圾回收算法,为了避免这个可以考虑参数,默认92%
⑷ JVM垃圾收集机制
JVM垃圾回收机制是java程序员必须要了解的知识,对于程序调优具有很大的帮助(同时也是大厂面试必问题)。
要了解垃圾回收机制,主要从三个方面:
(1)垃圾回收面向的对象是谁?
(2)垃圾回收算法有哪些?
(3)垃圾收集器有哪些?每个收集器有什么特点。
接下来一一讲解清楚:
一、垃圾回收面向的对象
也就是字面意思, 垃圾 回收嘛,重要的是垃圾,那什么对象是垃圾呢,简单来说就是无用的或已死的对象。这样又引申出来什么对象是已死的,怎么判断对象是否已死?
判断对象是否已死有两种算法和对象引用分类:
(1)引用计数算法:
也是字面意思,通过给对象添加引用计数器,添加引用+1,反之-1。当引用为0的时候,此时对象就可判断为无用的。
优点:实现简单,效率高。
缺点:无法解决循环引用(就是A引用B,B也引用A的情况)的问题。
(2)根搜索算法(也就是GC Roots):
通过一系列称为GC Roots的对象,向下搜索,路径为引用链,当某个对象无法向上搜索到GC Roots,也就是成为GC Roots不可达,则为无用对象。
如果一个对象是GC Roots不可达,则需要经过两次标记才会进行回收,第一次标记的时候,会判断是否需要执行finalize方法(没必要执行的情况:没实现finalize方法或者已经执行过)。如果需要执行finalize方法,则会放入一个回收队列中,对于回收队列中的对象,如果执行finalize方法之后,没法将对象重新跟GC Roots进行关联,则会进行回收。
很抽象,对吧,来一个明了的解释?
比如手机坏了(不可达对象),有钱不在乎就直接拿去回收(这就是没实现finalize方法),如果已经修过但是修不好了(已经执行过finalize方法),就直接拿去回收站回收掉。如果没修过,就会拿去维修店(回收队列)进行维修,实在维修不好了(执行了finalize方法,但是无法连上GC Roots),就会拿去回收站回收掉了。
那什么对象可以成为GC Roots呢?
1>虚拟机栈中的引用对象
2>本地方法栈中Native方法引用的对象
2>方法区静态属性引用对象
3>方法区常量引用对象
(3)对象引用分类
1>强引用:例如实例一个对象,就是即使内存不够用了,打死都不回收的那种。
2>软引用:有用非必须对象,内存够,则不进行回收,内存不够,则回收。例如A借钱给B,当A还有钱的时候,B可以先不还,A没钱了,B就必须还了。
3>弱引用:非必须对象,只能存活到下一次垃圾回收前。
4>虚引用:幽灵引用,必须跟引用队列配合使用,目的是回收前收到系统通知。
下面是java的引用类型结构图:
(1)软引用示例
内存够用的情况:
运行结果:
内存不够用的情况:
运行结果:
(2)弱引用示例结果:
无论如何都会被回收
(3)虚引用示例:
运行结果:
解释:为什么2和5的输出为null呢,如下
3为null是因为还没有进行gc,所以对象还没加入到引用队列中,在gc后就加入到了引用队列中,所以6有值。
这个虚引用在GC后会将对象放到引用队列中,所以可以在对象回收后做相应的操作,判断对象是否在引用队列中,可以进行后置通知,类似spring aop的后置通知。
二、垃圾回收发生的区域
垃圾回收主要发生在堆内存里面,而堆内存又细分为 年轻代 和 老年代 ,默认情况下年轻代和老年代比例为1:2,比如整个堆内存大小为3G,年轻代和老年代分别就是1G和2G,想要更改这个比例需要修改JVM参数-XX:NewRatio,
比如-XX:NewRatio=4,那老年代:年轻代=4:1。而年轻代又分为Eden区,S0(Survivor From)和S1(Survivor To)区,一般Eden:S0:S1=8:1:1,如果想要更改此比例,则修改JVM参数-XX:SurvivorRatio=4,此时就是Eden:S0:S1=4:1:1。
在年轻代发生GC称为Young GC,老年代发生GC成为Full GC,Young GC比Full GC频繁。
解析Young GC:
JVM启动后,第一次GC,就会把Eden区存活的对象移入S0区;第二次GC就是Eden区和S0一起GC,此时会把存活的对象移入S1区,S0清空;第三次GC就是Eden区和S1区进行GC,会把存活的对象移入S0区,如此往复循环15次(默认),就会把存活的对象存入老年区。
类似与如果有三个桶,编号分别为1(1号桶内的沙子是源源不断的,就像工地上。你们没去工地搬过砖可能不知道,但是我真的去工地上搬过啊),2,3。1里面装有沙子,需要将沙子筛为细沙。首先将桶1内的沙子筛选一遍过后的放置于桶2,第二次筛选就会将桶1和桶2里面的沙子一起筛,筛完之后放到桶3内,桶2清空。第三次筛选就会将桶1和桶3的沙子一起筛选,晒完放到桶2内,桶3清空。如此往复循环15次,桶2或桶3里面的沙子就是合格的沙子,就需要放到备用桶内以待使用。
上述中桶1就是Eden区,桶2就是S0区,桶3就是S1区。
三、垃圾回收算法
三种,分别是复制算法,标记-清除算法,标记-整理算法。
(1)复制算法。
其会将内存区域分成同样大小的两块,一块用来使用,另外一块在GC的时候存放存活的对象,然后将使用的一块清除。如此循环往复。
适用于新生代。
优点:没有内存碎片,缺点:只能使用一般的内存。
(2)标记-清除算法。
使用所有内存区域,在GC的时候会将需要回收的内存区域先进行标记,然后同意回收。
适用于老年代。
缺点:产生大量内存碎片,会直接导致大对象无法分配内存。
(3)标记-整理算法。
使用所有内存区域,在GC的时候会先将需要回收的内存区域进行标记,然后将存活对象忘一边移动,最后将清理掉边界以外的所有内存。
适用于老年代。
四、GC日志查看
利用JVM参数-XX:+PrintGCDetails就可以在GC的时候打印出GC日志。
年轻代GC日志:
老年代GC日志:
五、垃圾收集器
主要有四类收集器以及七大收集器
四类:
(1)Serial:单线程收集器,阻塞工作线程,它一工作,全部都得停下。
(2)Paralle:Serial的多线程版本,也是阻塞工作线程
(3)CMS(ConcMarkSweep):并行垃圾收集器,可以和工作线程一起工作。
(4)G1:将堆分成大小一致的区域,然后并发的对其进行垃圾回收。
怎么查看默认的收集器呢?
用JVM参数-XX:+PrintCommandLineFlags,运行之后会输出如下参数。可以看到,jdk1.8默认是Parallel收集器。
七大收集器:
(1)Serial:串行垃圾收集器,单线程收集器。用于新生代。用JVM参数-XX:+UseSerialGC开启,开启后Young区用Serial(底层复制算法),Old区用Serial Old(Serial的老年代版本,底层是标记整理算法)。
(2)ParNew:用于新生代,并行收集器。就是Serial的多线程版本。用JVM参数-XX:+UseParNewGC,young:parnew,复制算法。Old:serialOld,标记整理算法。-XX:ParallecGCThreads限制线程回收数量,默认跟cpu数目一样。只是新生代用并行,老年代用串行。
(3)Parallel Scavenge:并行回收收集器。用JVM参数-XX:+UseParallelGC开启,young:parallel scavenge(底层是复制算法),old:parallel old(parallel的老年代版本,底层是标记整理),新生代老年代都用并行回收器。
这个收集器有两个优点:
可控的吞吐量 :就是工作线程工作90%的时间,回收线程工作10%的时间,即是说有90%的吞吐量。
自适应调节策略 :会动态调节参数以获取最短的停顿时间。
(4)Parallel Old:Parallel Scavenge的老年代版本,用的是标记整理算法。用JVM参数-XX:+UseParallelOldGC开启,新生代用Parallel Scavenge,老年代用Parallel Old
(5)CMS(ConcMarkSweep):并发标记清除。 底层是标记清除算法,所以会产生内存碎片,同时也会耗cpu。 以获取最短回收停顿时间为目标。-XX:+UseConcMarkSweep,新生代用ParNew,老年代用CMS。CMS必须在堆内存用完之前进行清除,否则会失败,这时会调用SerialOld后备收集器。
初始标记和重新标记都会停止工作线程,并发标记和并发清除会跟工作线程一起工作。
(6)SerialOld:老年代串行收集器(以后Hotspot虚拟机会直接移除掉)。
(7)G1:G1垃圾收集器,算法是标记整理,不会产生内存碎片。横跨新生代老年代。实现尽量高吞吐量,满足回收停顿时间更短。
G1可以精确控制垃圾收集的停顿时间,用JVM参数-XX:MaxGCPauseMillis=n,n为停顿时间,单位为毫秒。
区域化内存划片Region,会把整个堆划分成同样大小的区域块(1MB~32MB),最多2048个内存区域块,所以能支持的最大内存为32*2048=65535MB,约为64G。
上图是收集前和收集后的对比,有些对象很大,分割之后就是连续的区域,也即是上图的Humongous。
上述理论可能有点乏味,下图很清晰明了(某度找的)。
下面来一张整个垃圾回收机制的思维导图(太大,分成两部分)。
=======================================================
我是Liusy,一个喜欢健身的程序猿。
欢迎关注【Liusy01】,一起交流Java技术及健身,获取更多干货。
⑸ JVM垃圾回收的“三色标记算法”实现,内容太干
三色标记法是一种垃圾回收法,它可以让JVM不发生或仅短时间发生STW(Stop The World),从而达到清除JVM内存垃圾的目的。JVM中的 CMS、G1垃圾回收器 所使用垃圾回收算法即为三色标记法。
三色标记法将对象的颜色分为了黑、灰、白,三种颜色。
白色 :该对象没有被标记过。(对象垃圾)
灰色 :该对象已经被标记过了,但该对象下的属性没有全被标记完。(GC需要从此对象中去寻找垃圾)
黑色 :该对象已经被标记过了,且该对象下的属性也全部都被标记过了。(程序所需要的对象)
从我们main方法的根对象(JVM中称为GC Root)开始沿着他们的对象向下查找,用黑灰白的规则,标记出所有跟GC Root相连接的对象,扫描一遍结束后,一般需要进行一次短暂的STW(Stop The World),再次进行扫描,此时因为黑色对象的属性都也已经被标记过了,所以只需找出灰色对象并顺着继续往下标记(且因为大部分的标记工作已经在第一次并发的时候发生了,所以灰色对象数量会很少,标记时间也会短很多), 此时程序继续执行,GC线程扫描所有的内存,找出扫描之后依旧被标记为白色的对象(垃圾),清除。
具体流程:
在JVM虚拟机中有两种常见垃圾回收器使用了该算法:CMS(Concurrent Mark Sweep)、G1(Garbage First) ,为了解决三色标记法对对象漏标问题各自有各自的法:
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求(但是实际由于某些问题,很少有使用CMS作为主要垃圾回收器的)。
从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于标记-清除算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为四个步骤,包括:1)初始标记(CMS initial mark) 2)并发标记(CMS concurrent mark) 3)重新标记(CMS remark) 4)并发清除(CMS concurrent sweep)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GCRoots能直接关联到的对象,速度很快;
并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;
重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短;
最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
在应对漏标问题时,CMS使用了增量更新(Increment Update)方法来做:
在一个未被标记的对象(白色对象)被重新引用后, 引用它的对象若为黑色则要变成灰色,在下次二次标记时让GC线程继续标记它的属性对象 。
但是就算是这样,其仍然是存在漏标的问题:
G1(Garbage First)物理内存不再分代,而是由一块一块的Region组成,但是逻辑分代仍然存在。G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待,如图所示
Card Table(多种垃圾回收器均具备)
RSet(Remembered Set)
是辅助GC过程的一种结构,典型的空间换时间工具,和Card Table有些类似。
后面说到的CSet(Collection Set)也是辅助GC的,它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。
在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。
而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。每个Region中都有一个RSet,记录其他Region到本Region的引用信息;使得垃圾回收器不需要扫描整个堆找到谁引用当前分区中的对象,只需要扫描RSet即可。
CSet(Collection Set)
一组可被回收的分区Region的集合, 是多个对象的集合内存区域。
新生代与老年代的比例
5% - 60%,一般不使用手工指定,因为这是G1预测停顿时间的基准,这地方简要说明一下,G1可以指定一个预期的停顿时间,然后G1会根据你设定的时间来动态调整年轻代的比例,例如时间长,就将年轻代比例调小,让YGC尽早行。
SATB(Snapshot At The Beginning), 在应对漏标问题时,G1使用了SATB方法来做,具体流程:
因为SATB在重新标记环节只需要去重新扫描那些被推到堆栈中的引用,并配合Rset来判断当前对象是否被引用来进行回收;
并且在最后G1并不会选择回收所有垃圾对象,而是根据Region的垃圾多少来判断与预估回收价值(指回收的垃圾与回收的STW时间的一个预估值),将一个或者多个Region放到CSet中,最后将这些Region中的存活对象压缩并复制到新的Region中,清空原来的Region。
会,当内存满了的时候就会进行Full GC;且JDK10之前的Full GC,为单线程的,所以使用G1需要避免Full GC的产生。
解决方案:
⑹ JVM的垃圾算法有哪几种
一、垃圾收集器概述
如上图所示,垃圾回收算法一共有7个,3个属于年轻代、三个属于年老代,G1属于横跨年轻代和年老代的算法。
JVM会从年轻代和年老代各选出一个算法进行组合,连线表示哪些算法可以组合使用
二、各个垃圾收集器说明
1、Serial(年轻代)
年轻代收集器,可以和Serial Old、CMS组合使用
采用复制算法
使用单线程进行垃圾回收,回收时会导致Stop The World,用户进程停止
client模式年轻代默认算法
GC日志关键字:DefNew(Default New Generation)
图示(Serial+Serial Old)
7、G1
G1收集器由于没有使用过,所以从网上找了一些教程供大家了解
并行与并发
分代收集
空间整合
可预测的停顿
⑺ jvm堆内存和非堆内存(小白入门文,各博客视频基础总结)
一:堆内存和非堆内存定义
Java虚拟机具有一个堆(Heap),堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是Java虚拟机启动时创建的。在JVM中堆之外的内u你成为非堆内存(Non-heap memory)。
堆内存以及相应垃圾回收算法
1.堆的大小可以固定,也可以扩大和缩小,堆内存不需要是连续空间。
2.对象创建后进入Eden。年轻代分为Eden和Survivor。Survivor由FromSpace和ToSpace组成。Eden区占大容量,Survivor占小容量,默认比例8:1:1。
MinorGC:采用复制算法。首先把Eden和ServivorFrom区域中存活的对象赋值到ServivorTo区域(如果对象年龄达到老年标准/ServivorTo位置不够了,则复制到老年代),同时对象年龄+1,然后清空Eden和ServivorFrom中的对象。然后ServivorTo和ServivorFrom互换。
3.老年代
老年代存放生命周期长的内存对象。
老年代对象相对稳定,所以不会频繁GC。在进行MajorGC前一般都先进行一次MinorGC,使新生代的对象进入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新晋的对象也会提前触发MajorGC进行垃圾回收。
MajorGC:如果使用CMS收集器,采用标记-清除算法。首先扫描老年代,标记所有可回收对象,标记完成后统一回收所有被标记对象。同时会产生不连续的内存碎片。碎片过多会导致以后程序运行需要分配较大对象时,无法找到足够的连续内存,而不得已再次出发GC。否则采用标记-压缩算法。
标记-压缩:在标记可回收对象后,将不可回收对象移向一端,然后清除标记对象。
当老年代也满了装不下时,抛出OOM异常。
二:永久代
内存中永久保存的区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域。他和存放实例的区域不同,GC不会再主程序运行期对永久区进行清理。所以也可可能导致永久代区域随着加载Class的增多而胀满,抛出OOM。
Java8中,永久代已经被移除,被一个成为“元数据区”(元空间)的区域所取代。
元空间的本质与永久代类似,都是JVM方法区的实现。不过元空间使用本地内存,永久代在JVM虚拟机中。因此,默认情况下,元空间的大小受本地内存限制。类的元数据放入native memory,字符串常量池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而是由系统实际可用空间控制。
1元空间解决了永久代的OOM问题,元数据和class对象在永久代容易出现性能问题和内存溢出。
2类的方法信息等比较难确定其大小,对于永久代的大小指定比较困难,小永久代溢出,大老年代溢出。
3永久代会为GC带来不必要的复杂度,回收效率低。
三:堆内存参数调优
1.-Xms 设置初始分配内存大小,默认物理内存1/64
2.-Xmx 设置最大分配内存,默认物理内存1/4
long maxMemory = Runtime.getRuntime().maxMemory(); long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("最大分配内存"+maxMemory/(double)1024/1024+"MB "+maxMemory/(double)1024/1024/1024+"GB"); System.out.println("默认分配内存"+totalMemory/(double)1024/1024+"MB "+totalMemory/(double)1024/1024/1024+"GB");
⑻ JVM有哪些垃圾回收算法
标记-清除,标记-复制,标记-整理
⑼ 你不得不知道的JVM 垃圾回收
一、四种引用方式
1.1 强引用
1.2 软引用(SoftReference)
1.3 弱引用(WeakReference)
1.4 虚引用(PhantomReference)
二、如何判断对象是垃圾
2.1 引用计数法
2.2 根可达性分析
三、垃圾回收算法
3.1 标记-清除(mark-sweep)
3.2 标记-整理(mark-compact)
3.3 标记-复制(mark-)
四、垃圾收集器
4.1 分类及特点简述
4.1.1 串行
4.1.2 吞吐量优先
4.1.3 响应时间优先
4.2 串行垃圾回收器详述
4.2.1 Serial
4.2.2 Serial-Old
4.2.3 流程图
4.3 吞吐量优先垃圾回收器详述
4.3.1 JVM相关参数
4.3.2 流程图
4.4、响应时间优先垃圾回收器详述
4.4.1 JVM相关参数
4.4.2 流程图
4.3.3 CMS的特点
五、G1垃圾回收器
5.1 相关JVM参数
5.2 特点
5.3 G1新生代垃圾回收
5.4 G1老年代垃圾回收
只有所有 GC Roots对象都不通过【强引用】引用该对象,该对象才可以被回收。
某个对象只要有一处引用关系,该对象的引用次数就加1,如果一个对象的引用次数为0,则说明该对象是垃圾。
优势:实现简单,效率较高
弊端:如果有一对对象之间形成了相互引用,但是这两个对象都已经没有被其它对象所引用了,正常情况下,这一对对象应该被作为垃圾回收掉,但是因为形成了相互引用导致无法被回收。
通过GC Root对象开始向下寻找,寻找不到的对象即说明没有被引用,那么这些没有被引用的对象被认定为垃圾。
目前,如下对象可以作为GC Root对象:
很好理解,即在GC的放生时候,先对所有对象进行根可达性分析,借此标记所有的垃圾对象;所有对象标记完毕之后会进行清理操作。
因此,总体来说,就是先标记再清除。
弊端;标记清除之后会产生大量不连续的内存碎片,碎片太多可能会导致程序运行过程中需要分配较大对象时,无法满足分配要求导致GC操作。
该回收算法操作过程基本等同于 标记-清除 算法只不过,第二步有点区别,该种方式会在清除的过程中进行 整理 操作,这是最大的不同。
优势:最终不会出现若干空间碎片而导致的空间浪费。
弊端:在整理过程中带来的计算不可小觑。
该种方式与前两种有较大的区别:
该种方式会将存储区分成两个部分,分别为From、To,其中From区域中可能存在着对象,而To区域始终为空,用做下一次接受数据做准备。
分别有两个指针指向这两个区域:From-Pointer、To-Pointer,
优点:这种算法非常适合早生夕死的对象
缺点:始终有一块内存区域是未使用的,造成空间的浪费。
特点:
特点:
特点:
JVM开关:-XX:+UseSerialGC = Serial + SerialOld
上图是:CMS垃圾回收器在老年代GC的工作流程图:
经过上面的文字分析,新生代的Region个数为所有Region个数的5%;这个数值其实是很小的,那么当新生代Region不够用的时候,JVM会划分更多的Region个数给新生代;
当新生代的Region个数占比所有Region个数超过 60% 时,就会进行一次新生代的垃圾回收。
新生代垃圾回收会造成STW。
具体的垃圾回收算法同其它几个新生代垃圾回收器一样,新生代都使用复制算法。
老年代垃圾回收触发机制与参数-XX:InitaingHeapOccupancyPercent有关。
但是需要注意的是:这一次的老年代回收,其实是一次混合垃圾回收,会同时清理新生代、老年代、Humongous。
与新生代回收算法一致,依然使用复制算法,但是垃圾回收的过程等同于老年代响应时间优先的CMS方式
流程分为:
⑽ jvm的垃圾回收机制详解
1.JVM的gc概述
gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作。
在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率。理解了应用程序的工作负荷和jvm支持的垃圾收集算法,便可以进行优化配置垃圾收集器。
垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。gc首先要判断该对象是否是时候可以收集。两种常用的方法是引用计数和对象引用遍历。
1.1.引用计数
引用计数存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,jvm必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。
1.2.对象引用遍历
早期的jvm使用引用计数,现在大多数jvm采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。
下一步,gc要删除不可到达的对象。删除时,有些gc只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多gc可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。
为此,gc需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有gc运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的 gc不断增加或同时运行以减少或者清除应用程序的中断。有的gc使用单线程完成这项工作,有的则采用多线程以增加效率。
2.几种垃圾回收机制
2.1.标记-清除收集器
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。
2.2.标记-压缩收集器
有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。
2.3.复制收集器
这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,jvm生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。
2.4.增量收集器
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。
2.5.分代收集器
这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。jvm生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。
2.6.并发收集器
并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。
2.7.并行收集器
并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显着的提高java应用程序的可扩展性。