一. 判断对象已死的方法
- 引用计数法
(1)思路:给对象添加一个计数器,每当一个地方引用它,计数器就+1,当一个引用失效,计数器-1. 计数器值为0的对象,背叛定位已死对象
(2)缺点:引用计数器无法处理处理互相引用的问题。当两个对象互相引用时,即使这两个对象被置为null,但是由于互相引用着对方,导致各自的引用计数器不为0,使得这2个对象无法被标记未死亡对象 - 可达性分析
(1)思路:通过一系列成为“GC ROOTs”的对象作为起点,从这些节点开始向下搜索,所走过的路径乘坐引用链。如果一个对象和GCRoots之间没有任何的引用链,则对象被看做已死对象
(2)可当做GCRoots的对象- java栈中引用的对象(栈帧的本地变量表)
- 静态区中静态属性引用的对象
- 静态区中常量引用的对象
- native栈中引用的对象
【注】:java采用可达性分析来区分对象到底是否死亡
二. 判断对象死亡的过程(2次标记)
不可达的对象并不是一定会被gc掉的。当对象被视作为不可达后,jvm对该对象进行第一次标记,如果jvm发现该对象的finalize()方法从未被调用过,则jvm会把对象放到F-Queue中,稍后,jvm会建立一个优先级很低的Finalizer线程执行该方法。如果对象在finalize()方法中把this与引用链上的任何一个对象关联,则他会被移出“即将回收集合”,没被移出集合的对象备进行二次标记,经过两次标记的对象在gc时会被垃圾回收
【注】:对象的finalize方法被一个优先级很低的线程执行,如果在该线程还未执行finalize方法前,就被gc垃圾回收,则该对象的finalize方法并不会产生任何效果。所以覆盖finalize方法拯救对象的方式,并不一定每次有效
三. GC策略
- 标记清除算法
2次标记后jvm标记了需要gc的对象,标记清除算法直接清空这些对象的内存地址。这样做带来的弊端就是,内存中产生大量不连续的空间,当需要产生大对象时,有雨没有足够的连续内存导致新一轮的gc产生 - 复制算法
(1)将内存飞卫等大小的2块,一块内存用完,就把存活的对象放到另一块内存中,然后清空该块内存。这种方法不会产生碎片,但是实际可用内存减少到原来的一半
(2)JVM采用复制算法回收新生代的对象,但jvm没有将你新生代内存等分,而是划分为一个大的eden区和2个小的survive区。HotSpot中的比例时8:1:1,新生代中的内存浪费10%。每次垃圾回收时都会拷贝eden和from survive中的存活对象到to survive中,然后清空这两块内存。如果to survive空间不够用,则这些对象就进入老年代。这就是老年代的分配担保 - 标记整理
复制算法适合gc新生代,因为新生代的对象大部分不会存活,如果采用复制算法gc对象存活率很高的老年代,会使得复制操作开销很多。因此,老年代采用标记整理算法进行gc。标记整理算法在标记清除之后,把对象向内存的一端移动,然后清理掉端边界意外的内存。
四. 内存分配策略
- 对象优先在新生代的eden区分配
对象先尝试在eden区生成,当eden区剩余空间不足以产生新的对象时,触发minorgc:eden和from survive的存活对像进入to survive中。如果to survive没有足够容量,则存放不下的对象放到老年代中,清空eden和from survive区域。 - 大对象直接在年老代产生
本来对象要在eden产生,但是大对象会在minorgc时产生大量的复制开销,不利于gc,因此jvm把大对象直接在年老代产生。以通过参数-XX:PretenureSizeThreshold设置进入年老代的对象大小上限。(该参数只对Serial和ParNew收集器有效,Paral ScanVege手机其不认识这个参数) - 长期存活对象进入年老代
每次存活对象经历一次minorgc后,对象年龄就+1,默认经过15岁后,对象进入老年代,可以通过参数-XX:MaxTenuringThreshold设置。
jvm在年龄判断时进行了优化,如果from survive中相同年龄的对象综合大于from survive总大小的一半,则大于等与该年龄的对象直接进入老年代,而不用等到年龄上线到达以后在进入老年代,节省了minorgc的复制开销 - 老年代的空间担保
minorgc后,如果to survive无法存放eden和from survive的存活对像,则这些对象尝试进入老年代,如果老年代的剩余空间也无法存放这些对象,则会查看HandlePromotionFailure参数是否设置true。如果设置true,证明允许担保失败,接着如果发现老年代的最大可用连续空间>历次进入老年代的对象平均大小,则冒险进行一次minorgc;如果发现老年代的最大可用连续空间<历次进入老年代的对象平均大小,或者不允许担保失败,则进行一次full gc(full gc触发stop the world,堆新生代,老年代,永久带全部进行一次gc)。若果还是无法分配到连续内存,则抛出oom
五. 垃圾回收器的实现
-
Serial单线程收集器
(1)Serial采用1个线程回收垃圾对象,Serial的gc触发时产生stop the world
(2)Serial回收器采用复制算法收集新生代,采用标记整理算法收集老年代
(3)Serial收集器时Client模式下的默认收集器,简单高效。虽然触发stop the world,但是回收一两百m的对象大概耗时100毫秒
(4)SerialOld时老年代收集器 -
ParNew新生代收集器
(1)多线程版的Serial收集器,其他特点和Serial一模一样,例如stop the world
(2)多核心cpu下ParNew效果比Serial好,单核cpu下Serial比ParNew好
(3)只有Serial和ParNew新生代回收器可以和CMS老年代回收器组合使用
(4)parNew是server模式下的最佳新生代收集器
(5)ParOld是对应的老年代收集器 -
Parall Scavenge新生代收集器
(1)Parall Scavenge收集器侧重于jvm的吞吐量((吞吐量=frac{用户代码运行时间}{用户代码运行时间+垃圾回收线程执行时间}))
(2)Parall Scavenge可以自适应调节回收参数,需要把基本的内存数据(堆最大,最小量)设置好,然后设置更关注最大停顿时间或者更关注吞吐量,收集器会把细节参数自动调节。 -
CMS(concurrent mark sweep)并发老年代收集器
(1)基于标记清除算法回收老年代- 初始标记:仅标记GCRoots能直接关联到的对象,速度很快,但是“stop the world”
- 并发标记:GCRoot Tracing,标记GCRoots的二级三级关联对象。4步中耗时最长但是和用户线程并发执行
- 重新标记:修正并发标记时用户线程到只得标记变动,“stop world”且停顿稍长
- 并发清除:耗时长,和用户线程同步执行
(2)CMS收集器的缺点
- CMS收集器虽然是并发执行,但会降低用户线程的响应速度
- 浮动垃圾无法清除:cms回收线程和用户线程并发执行,用户线程还会产生新的垃圾对象,旧垃圾对象还未清除完毕,新垃圾对象的到来导致新一次的gc,这些旧垃圾对象称为浮动垃圾。通过-XX+CMSInitiatingOccupancyFraction=value来设置老年代空间沾满百分之多少后就触发majorgc。如果该百分比设置的太高,cms预留的老年代空间无法承载新到来得老年代对象,则发生“Concurrent Mode Faliure”,jvm临时采用Serial old回收器回收,产生stop the world效果
- cms采用标记清除算法回收对象,会在老年代产生内存碎片,分配大对象时找不到连续空间而触发full gc。通过设置XX+UseCMSCompactAtFullCollection(默认为开启) ,让cms触发fullgc时进行内存碎片整理,但使得停顿时间增大
-
g1收集器
g1收集器用来替代cms收集器
(1)并行与并发:利用多cpu缩短stop-the-world的时间,使用并发方式解决其它收集器需要停顿的gc动作。
(2)分代收集:新老代收集区分对待。
(3)空间整合:G1从整理看是基于标记-整理,但是局部看是基于复制算法实现的,不会产生碎片。
(4)可预测的停顿:能够让使用者指定在M毫秒的时间片段上,消耗在垃圾回收的时间不得超过N毫秒。过程:初始标记、并发标记、最终标记、筛选回放。前三个和CMS一致,筛选回放是根据用户设置的停顿目标来选择回收价值最高的进行回收。
六. fullgc频繁触发的原因
-
永久带空间沾满
解决方法:增大永久带,或选择cms收集器 -
CMS触发了promotion failed和concurrent mode failure
解决方法:增大老年代或减小cms进行majorgc时的百分比
JAVA_OPTS="$JAVA_OPTS -Xms2g -Xmx2g -Xmn768m -Xss256K -XX:PermSize=256m -XX:MaxPermSize=256m -XX:SurvivorRatio=2"
JAVA_OPTS="$JAVA_OPTS -XX:+DisableExplicitGC -XX:+UseParNewGC"
JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSClassUnloadingEnabled -XX:+UseCMSInitiatingOccupancyOnly"
JAVA_OPTS="$JAVA_OPTS -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:CMSInitiatingOccupancyFraction=55"
JAVA_OPTS="$JAVA_OPTS -XX:SoftRefLRUPolicyMSPerMB=0 -XX:LargePageSizeInBytes=128M"
JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:-OmitStackTraceInFastThrow -XX:-UseGCOverheadLimit"
JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy -Xloggc:$ROOT/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$ROOT"