对象的迁徙 从幼儿园(新生代)到养老院(老年代)
- 一般情况:
对象优先分配在新生代
新生代如果对象满了,会触发Minor GC(Young GC)回收掉没有被引用的垃圾对象
如果有对象躲过了十多次垃圾回收,就会放入老年代里
如果老年代也满了,那么也会触发垃圾回收,把老年代里没人引用的垃圾对象清理掉
- 复杂情况:
新生代垃圾回收之后,因为存活对象太多,导致大量对象直接进入老年代
特别大的超大对象直接不经过新生代就进入老年代
动态对象年龄判断机制
空间担保机制
回收机制
- 无GCRoots引用的对象回收。
- 有GCRoots引用
2.1 强引用不回收;
2.2 软引用,空间不够依然回收。
什么时候回收
对象太多,空间不够了,优先从年轻代里的对象开始回收
回收哪些对象
通过可达性分析算法来筛选回收目标,追踪引用链的根对象是否属于GCRoots.
还要进一步通过引用类型来判断。
GCRoots:
- 线程栈中的局部变量
- 方法区的静态变量
引用类型
有GCRoots引用就不回收么?不是的,还要看引用的类型,java中引用的类型分为以下几种:
- 强引用
强引用 不回收 - 软引用
new SoftReference<T>(new T());正常回收后,空间还不够,就回收软引用的对象(可达)。 - 弱引用
跟无引用情况类似 - 虚引用
跟无引用一样吧?少见。
最后的希望
回收前,调用对象的finialize方法,若 重写Object的finialize()方法并把自己的引用关联到GCRoots上,那么久不回收了。
年轻代GC
1.算法演进
1.1 标记清除
会产生内存空间碎片,不连续的空闲空间。
1.2 复制算法
把剩下的对象拷贝到另一块内存空间,连续排列,避免碎片。
- 内存空间一分为二,同时只使用其中一个
- 空间比较浪费,同时只使用其中一个
- 存活的对象比较小,占用一半空间太浪费
1.3 复制算法优化
1个Eden +2个Survivor ,通常空间比例为:8:1:1
- 新对象占比高,给与较大的空间,使用Eden。
- 存活的对象占比少,给与较小的空间,使用Survivor。
- Eden和suvivor中存活的对象,复制到另外一个Survivor中。
- 只闲置一个Survivor的空闲,空间浪费少
Minor GC
Minor GC的执行时机
简单理解MinorGC :Eden满了,触发MinorGC,把两个内存区域中的存活对象都迁移到另一个Survivor中。
1. 默认经历15次MinorGC,存活对象进入老年代
这个具体是多少岁进入老年代,可以通过JVM参数“-XX:MaxTenuringThreshold”来设置,默认是15岁。
2. 动态对象年龄判断
当前 Survivor区域中,年龄1+年龄2+年龄n的多个年龄段的对象大小总和超过了Survivor区域的50%(阈值可调),此时会把年龄n以上的对象都放入老年代。
注意:
若要避免此种情况,
- 增加Survivor大小
- 若新生代内存有限,那么可以调整"-XX:SurvivorRatio=8"这个参数,默认是说Eden区比例为80%,也可以降低Eden区的比例,给两块Survivor区更多的内存空间,然后让每次Minor GC后的对象进入Survivor区中,还可以避免动态年龄判定规则直接把他们升入老年代。
3. 大对象直接进入老年代
有一个JVM参数,就是“-XX:PretenureSizeThreshold”,可以把他的值设置为字节数,比如“1048576”字节,就是1MB。
如果新创建的对象大小超过了这个阈值,则直接放置到老年代里去。
思考:如果要避免一次性大对象出现在Old GC中,考虑增加年轻代大小。
4. Minor GC后,对象太多Survivor放不下
这些对象直接转移到老年代。
注意:
若这些对象也将很快就释放,考虑增加Survivor区域大小。
5. 老年代空间分配担保规则 ,Minor GC前的检查
问题,如果老年代空间不足时,怎么处理?
Minor GC前,会判断老年代可用空间大小 是否大于 新生代所有对象总大小,即老年代空间够不够。
- 空间足够:立即执行MinorGC;此时,即使Survivor空间不够,需要直接转移到老年代也是可以的。
- 空间不足:
2.1 看老年代的内存大小,是否大于之前每一次Minor GC后进入老年代的对象的平均大小。
2.1.1 老年代剩余大小 > 历次Minor GC的平均大小,则触发Minor GC
2.1.2 老年代剩余大小 < 历次Minor GC的平均大小, 则触发 Full GC,释放老年代空间后,执行Minor GC
留意:在JDK 6 Update 24之后,HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略
Minor GC的结果
- Minor GC后,都能放入Survivor中
- Minor GC后,Survivor中放不下,老年代能放下,直接放入老年代中
- Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内存的大小。此时老年代都放不下这些存活对象了,这个时候就会触发一次“Full GC”。
Full GC
场景:
- Minor GC前
检查老年代空间,若检查失败(不足以放下存活的新生代对象),则执行Full GC,然后执行Minor GC - Mino GC后,老年代的空间不足以存放 存活的新生代对象,则执行Full GC
Full GC后的结果:
- Full GC过后,老年代空间足够支撑MinorGC回收的年轻代对象,(如果是MinorGC前检测老年代空间不够引发的Full GC,则之后会接着执行Minor GC).
- 老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么会触发OOM
老年代GC算法
标记整理算法
把剩下的数据布局在连续的空间中,避免内存碎片
对比 “标记清除”算法,虽然会避免空间碎片,但是整理到引起更多的内存拷贝。
老年代算法比新生代的慢很多,可能是10倍,所以Full GC会出现系统卡顿。
垃圾回收器
- Serial和Serial Old垃圾回收器
- 分别是新生代和老年代的 回收工作。
- 单线程工作,性能不好
- ParNew和CMS垃圾回收器:
- 一般分别用于新生代和老年代的回收工作
- 多线程并发处理,性能好,一般是线上生产系统的标配组合
- G1垃圾回收器:
统一收集新生代和老年代,采用更加优秀的算法和设计机制
网友评论