垃圾回收的总结

1、可达性与引用html

GCRoots:算法

虚拟机栈中引用的对象安全

方法区中类静态属性引用的对象数据结构

方法区中常量引用的对象并发

本地方法栈中JNI引用的对象性能

 

强引用:最多见的引用优化

软引用:系统内存要溢出了回收spa

弱引用:对象未被引用就收集了.net

虚引用:被找到就直接收集掉线程

 

finalize方法:

宣告一个对象死亡至少有经历两次标记

第一次标记,判断他是否有必要finalize(),若对象未被覆盖finalize()方法或被虚拟机执行过了,则认为不必执行;如有必要执行会被加入F-Queue队列中,GC稍后再标记一次。

每一个对象finalize方法只能执行一次,由于自救只能救一次

 

2、垃圾收集算法与收集器

标记-清除

复制-清除 

标记-整理

新生代空间大,GC后存活量少,因此多数用复制;老年代空间小,存活率高,没有分配担保,用标记

 

GC须要再某个一致性的快照(仿佛系统被冻结的时间点)进行分析,所以必需要作Stop The World。HotSpot中经过OopMap数据结构记录(待补完)

 虚拟机在一些特定指令位置设置一些“安全点”,当程序运行到这些“安全点”的时候就会暂停全部当前运行的线程(Stop The World 因此叫STW),暂停后再找到“GC Roots”进行关系的组建,进而执行标记和清除。 
  这些特定的指令位置主要在:

一、循环的末尾
二、方法临返回前 / 调用方法的call指令后
三、可能抛异常的位置

 

收集器

年轻代:

a.串行 Serial,串行执行,Stop The World,JDK 1.3.1前惟一选择,单CPU甚至双CPU下比并行要好,

b. 并行 ParNew,并行执行,Stop The World

c. Parallel Scavenge,并行执行,经过参数 -XX:MaxGCPauseMillis控制最大停顿时间,-XX:GCTimeRatio控制吞吐量。但他并不能和CMS配合

老年代

d. Serial Old,做为CMS的后备,当CMS浮动垃圾太多而Concurrent Mode Failure时

e. Parallel Old

f. CMS :标记——清除,将垃圾回收的过程分为4个步骤

初始标记:Stop The World,仅标记可关联对象

1. 标记老年代中全部的GC Roots对象
2. 标记年轻代中活着的对象引用到的老年代的对象(指的是年轻带中还存活的引用类型对象,引用指向老年代中的对象)

ps:为了加快此阶段处理速度,减小停顿时间,能够开启初始标记并行化,-XX:+CMSParallelInitialMarkEnabled,同时调大并行标记的线程数,线程数不要超过cpu的核数;

并发标记:耗时长,与用户线程一块儿跑

从“初始标记”阶段标记的对象开始找出全部存活的对象;

由于是并发运行的,在运行期间会发生新生代的对象晋升到老年代、或者是直接在老年代分配对象、或者更新老年代对象的引用关系等等,对于这些对象,都是须要进行从新标记的,不然有些对象就会被遗漏,发生漏标的状况。为了提升从新标记的效率,该阶段会把上述对象所在的Card标识为Dirty,后续只需扫描这些Dirty Card的对象,避免扫描整个老年代; 
并发标记阶段只负责将引用发生改变的Card标记为Dirty状态,不负责处理

这个阶段由于是并发的容易致使concurrent mode failure

 

预清理

 

经过参数CMSPrecleaningEnabled选择关闭该阶段,默认启用,主要作两件事情:

处理新生代已经发现的引用,好比在并发阶段,在Eden区中分配了一个A对象,A对象引用了一个老年代对象B(这个B以前没有被标记),在这个阶段就会标记对象B为活跃对象。

在并发标记阶段,若是老年代中有对象内部引用发生变化,会把所在的Card标记为Dirty(其实这里并不是使用CardTable,而是一个相似的数据结构,叫ModUnionTalble),经过扫描这些Table,从新标记那些在并发标记阶段引用被更新的对象(晋升到老年代的对象、本来就在老年代的对象)

 

可中断的预清理 AbortablePreclean

该阶段发生的前提是,新生代Eden区的内存使用量大于参数CMSScheduleRemarkEdenSizeThreshold 默认是2M,若是新生代的对象太少,就没有必要执行该阶段,直接执行从新标记阶段

由于CMS GC的终极目标是下降垃圾回收时的暂停时间,因此在该阶段要尽最大的努力去处理那些在并发阶段被应用线程更新的老年代对象,这样在暂停的从新标记阶段就能够少处理一些,暂停时间也会相应的下降。

在该阶段,主要循环的作两件事:

  1. 处理 From 和 To 区的对象,标记可达的老年代对象
  2. 和上一个阶段同样,扫描处理Dirty Card中的对象

 

从新标记:Stop The World

这个阶段会致使第二次stop the word,该阶段的任务是完成标记整个年老代的全部的存活对象。 
这个阶段,从新标记的内存范围是整个堆,包含_young_gen和_old_gen。为何要扫描新生代呢,由于对于老年代中的对象,若是被新生代中的对象引用,那么就会被视为存活对象,即便新生代的对象已经不可达了,也会使用这些不可达的对象当作cms的“gc root”,来扫描老年代; 所以对于老年代来讲,引用了老年代中对象的新生代的对象,也会被老年代视做“GC ROOTS”:当此阶段耗时较长的时候,能够加入参数-XX:+CMSScavengeBeforeRemark,在从新标记以前,先执行一次ygc,回收掉年轻带的对象无用的对象,并将对象放入幸存带或晋升到老年代,这样再进行年轻带扫描时,只须要扫描幸存区的对象便可,通常幸存带很是小,这大大减小了扫描时间 

并发清除:开始清除,耗时长,与用户线程一块儿跑,产生浮动垃圾

 

CMS问题:

对CPU敏感:CMS默认线程数是(CPU数 + 3)/4 ,就是4个以上时收集器有25%以上的cpu资源,当CPU不足四个时就会影响用户程序。有种增量式并发收集器i-CMS,经过抢占式来处理,使得垃圾收集时间更长,但对用户程序影响小点

浮动垃圾:并发清理时用户线程在跑,就可能有浮动垃圾,使得CMS不能像其余收集器那样老年代几乎满了再收集。1.5默认用了68%就会激活,1.6是92%,经过-XX:CMSInitiatingOccupancyFraction的值控制百分比。CMS运行期预留内存没法知足程序须要,就会Concurrent Mode Failure,就是启动预备的Serial Old,再不行就Full GC

内存碎片:由于标记清除致使内存碎片。CMS提供一个-XX:+UseCMSCompactAtFullConllection开关,决定FullGC前作不作压缩(STW),还有一个-XX:CMSFullGCsBeforeCompaction 决定多少次不压缩的Full GC后来一次压缩

 

G1 收集器

新的Garbage-First(G1)GC就回到了以copying为基础的算法上,把整个GC堆划分为许多小区域(region),经过每次GC只选择收集不多量region来控制移动对象带来的暂停时间。这样既能实现低延迟也不会受碎片化的影响

(待补充)

 

3、内存分配与回收策略

主要分配在Eden,若是启动了本地线程分配缓冲,按线程优先在TLAB上。

Eden没有足够空间使进行一次Minor GC

大对象直接进入老年代,长期存活的对象进入老年代。每一个对象有一个Age计数器,Eden中通过一次GC仍然存活并能被Survivor容纳,移动到Survivor,年龄记为1,而后每次Minor GC对象加一,默认15次到老年代(参数 -XX:MaxTenuringThreshold)

注:年龄判断不是绝对的,Survivor空间中相同年龄全部对象大小大于Survivor的一半时,年龄大于等于这个的对象就直接进入老年代

 

空间分配担保

Minor GC前检查老年代最大可用连续空间十分大于新生代全部对象总空间,成立则绝对安全;

不然,查看虚拟机参数HandlePromotionFailure是否容许担保失败,若是容许,查询老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小,大于则试一把Minor GC;若小于且不容许担保,则Full GC

 

4、问题总结

一、为何有两个Survivor

设置两个Survivor区最大的好处就是解决了碎片化

为何一个Survivor区不行?第一部分中,咱们知道了必须设置Survivor区。假设如今只有一个survivor区,咱们来模拟一下流程: 
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,若是此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就致使了内存碎片化。 
碎片化带来的风险是极大的,严重影响JAVA程序的性能。堆空间被散布的对象占据不连续的内存,最直接的结果就是,堆中没有足够大的连续内存空间,接下去若是程序须要给一个内存需求很大的对象分配内存。。。画面太美不敢看。。。这就比如咱们登山的时候,背包里全部东西紧挨着放,最后就可能省出一块完整的空间放相机。若是每件行李之间隔一点空隙乱放,极可能最后就要一路把相机挂在脖子上了。

那么,瓜熟蒂落的,应该创建两块Survivor区,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程很是重要,由于这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。S0和Eden被清空,而后下一轮S0与S1交换角色,如此循环往复。上述机制最大的好处就是,整个过程当中,永远有一个survivor space是空的,另外一个非空的survivor space无碎片。

 

二、Full GC的触发条件与优化

(1)调用System.gc时,系统建议执行Full GC,可是没必要然执行

(2)老年代空间不足

(3)方法去空间不足

(4)经过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

避免Full GC : https://blog.csdn.net/endlu/article/details/51144918

 

三、jdk 1.8 JVM的更新

https://www.cnblogs.com/sxdcgaq8080/p/7156227.html

相关文章
相关标签/搜索