回顾
上一篇说了垃圾回收时机,强引用、软引用、弱引用、虚引用
新生代的垃圾回收算法
新生代使用的是复制算法
新生代分为3块区域
默认比例为8:1:1
eden:s1:s2
平时可以使用的,就是Eden区和其中一块Survivor区
但是刚开始创建的对象分配在 eden 区的
如果eden区垃圾对象越来越多
此时会触发垃圾回收机制
具体做法
复制算法
会先标记存活的对象
然后把存活的对象复制到另外一个区域,例如s1
放到另外一个区域可以紧凑的排列在一起,不会产生什么内存碎片
这个时候把eden区域的内存空间可以一扫而空
那么下次又有新对象后
会直接分配到eden区域
这个时候 eden 区和 s1 都是有对象的
s1 存放的是 上次 Minor GC 之后存活的对象
等到下次 eden 快满的时候
就会把 eden 区和 s1 存活的对象复制到s2区域
然后把 eden 和 s1 区域清空
如此循环往复
这么做最大的好处是 90%的空间都被使用到了,只有10%是空闲的
无论是垃圾回收的性能,内存碎片的控制,还是说内存使用的效率,都非常的好
进入老年代的时机
新生代默认进入老年代,是要等 15 次 GC 之后
也可以通过JVM参数来调整进入老年代的时机"-XX:MaxTenuringThreshold"
动态年龄判断
这是进入老年代的另外一个规则
就是说当要转移到S区域的对象的总大小,大于这块S区域50%,会直接进入老年代
大对象直接进入老年代
有一个JVM参数,就是“-XX:PretenureSizeThreshold”,可以把他的值设置为字节数,比如“1048576”字节,就是1MB。
他的意思就是,如果你要创建一个大于这个大小的对象,比如一个超大的数组,或者是别的啥东西,此时就直接把这个大对象放到老年代里去。压根儿不会经过新生代。
之所以这么做
是因为如果这么大的对象,屡次躲过GC,在两个s区域来回复制,然后进入了老年代
这么大的对象在里面复制,很消耗时间
Minor GC后的对象太多无法放入Survivor区怎么办
如果在Minor GC之后发现剩余的存活对象太多了,没办法放入另外一块Survivor区怎么办
这时候会直接把这批对象放入老年代
老年代空间分配担保
新生代存活大量对象,s区域存放不了
必须转移到老年代去
那如果老年代也放不下怎么办
首先,在执行任何一次Minor GC之前,JVM会先检查一下老年代可用的可用内存空间,是否大于新生代所有对象的总大小。
目的是为了避免新生代所有对象都存活下来转移到老年代
如果大于新生代大小,老年代就可以存放新生代存活的对象了
那如果老年代的空间小于新生代存活对象的大小
理论上有这个可能的
所有这个时候如果JVM发现老年代的可用内存已经小于了新生代的全部对象大小了,就会看一个“-XX:-HandlePromotionFailure”的参数是否设置了
如果有这个参数,那么就会继续尝试进行下一步判断。
下一步判断,就是看看老年代的内存大小,是否大于之前每一次Minor GC后进入老年代的对象的平均大小。
举个例子,之前每次Minor GC后,平均都有10MB左右的对象会进入老年代,那么此时老年代可用内存大于10MB。
这就说明,很可能这次Minor GC过后也是差不多10MB左右的对象会进入老年代,此时老年代空间是够的
如果上面那个步骤判断失败了,或者是“-XX:-HandlePromotionFailure”参数没设置,此时就会直接触发一次“Full GC”,就是对老年代进行垃圾回收,尽量腾出来一些内存空间,然后再执行Minor GC。
如果上面两个步骤都判断成功了,那么就是说可以冒点风险尝试一下Minor GC。此时进行Minor GC有几种可能。
第一种可能,Minor GC过后,剩余的存活对象的大小,是小于Survivor区的大小的,那么此时存活对象进入Survivor区域即可。
第二种可能,Minor GC过后,剩余的存活对象的大小,是大于 Survivor区域的大小,但是是小于老年代可用内存大小的,此时就直接进入老年代即可。
第三种可能,很不幸,Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内存的大小。此时老年代都放不下这些存活对象了,就会发生“Handle Promotion Failure”的情况,这个时候就会触发一次“Full GC”。
Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。
因为这个时候必须得把老年代里的没人引用的对象给回收掉,然后才可能让Minor GC过后剩余的存活对象进入老年代里面。
如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么就不会爆臭名昭著的OOM了
老年代垃圾回收算法
老年代触发垃圾回收的时机,一般是2个
在Minor GC之前,检查发现很可能Minor GC之后要进入老年代的对象太多了,老年代放不下,此时需要提前触发Full GC然后再带着进行Minor GC
在Minor GC之后,发现剩余对象太多放入老年代都放不下了
老年代采用的是标记整理算法
首先标记出来存活的对象
然后会把这些存活的对象都尽量挪到一边
避免垃圾回收之后又过多的内存碎片
然后在一次性把垃圾对象回收
老年代的垃圾回收比新生代慢10倍
如果系统频繁出现Full GC 会严重影响系统性能,出现频繁卡顿
JVM优化
就是尽可能的让对象都在新生代进行分配和回收
尽量别让太多对象频繁进入老年代
避免频繁对老年代进行垃圾回收
同时给足充分的内存大小
避免新生代频繁的进行垃圾回收