JVM垃圾收集原则和调优

关于JVM的垃圾收集,首先应该明白对象在堆中的几个原则,之前说的那些是最基本的情况,现在来说的是特殊情况,有时产生莫名其妙的fullgc可能就是源于他们,弄清楚他们才能避免意料之外的fullgc。第一个原则是最基础的,对象优先会在eden区分配,当然会有特殊情况,比如大对象,比如逃逸分析,后面再展开说,因为这个原则,我们需要让年轻代尽可能得大,而老年代够用就行。第二个原则是有的对象会在一次gc后就进入老年代,原因就在于Eden区往往比from区和to区大很多,就会出现Eden区发生minorgc后对象比from区大,放不进去,这样的对象就会直接进入老年区,这是需要通过设置分区大小来优化的点。第三个原则和第一个原则一样很基础,长期存活的对象,年龄达到15就会进入老年代,但往往可以通过参数,结合实际情况,把他调小。原则四也是之前提过的,大对象直接在老年代出生,这样做的原因是,大对象太大,如果在Eden区出生,容易触发太多次的gc,而且在年轻代的三个分区中反复转移,会消耗性能,那么多大的对象是大对象呢,并不是说Eden区放不下才叫大对象,而是这是可以通过参数设置的,是个优化的点,分析哪些大对象是一定会进入老年代的,就通过参数让他们直接在老年代出生。

原则五和六比较重要。原则五是对象动态年龄判断,这里体现了jvm的智能性,之前说过,那个年龄设置得太死板了,所以就有了对象动态年龄判断,他就是一个算法,通俗得来说,就是在survivor区找这个阈值年龄,超过阈值年龄的对象直接进入老年代,所以阈值的确定就很关键了,他是通过统计survivor区中各年龄段的对象大小,然后从小到大累加,当某个年龄以下的对象占整个空间的百分之五十,那么这个年龄就是阈值年龄,比这个年龄大就直接进入老年代。这样做的好处是提前让应该进入老年代的对象尽早进入老年代。和原则三的好处一样,由这个算法可以知道,他们占的空间也往往比较大,所以能给新的对象提供空间,来回复制也浪费性能。值得注意的是,一个百分之五十是无法通过参数来设置的。

原则六就比较细节,体现了jvm无时无刻都在想着优化。他是老年代空间分配担保机制,场景是一次minorgc后,会往老年代放入对象,从而可能紧接着触发fullgc,两次gc加起来,应用程序的停顿的时间就可能让用户接受不了,所以jvm默认的优化方式是判断会出现这种情况时,就会省去那次minorgc,而是直接进行fullgc,在每次minorgc之前,会老年代的剩余可用空间和年轻代包括垃圾所在的空间,这里就有了优化的操作空间了,如果小于,就有可能省去这次minorgc,来直接fullgc。jvm默认的这种策略初衷是好的,但是会产生很多莫名其妙的fullgc,我们优化的原则是尽可能少得触发fullgc,所以jvm提供了参数可以来说老年代空间担保机制,从而减少这次fullgc而使用minor代替,有点辩证思维,把决定权交给了我们程序员。而这个参数是在1.8后默认开启的。他的具体做法是在fullgc之前设置了两道坎,一道就是这个参数是否开启,未开启就fullgc,开启后还有一道就是判断之前的minorgc给老年代装入对象的平均大小,注意,他是不包括垃圾对象的,如果够装,就minorgc而非fullgc。

总结一下老年代空间分配担保原则,他经过三次判断,一是判断老年代剩余空间是否大于年轻代包含垃圾对象的全部空间,二是判断是否设置了担保参数,三是判断老年代剩余可用空间是否小于之前minor进入老年代的平均空间大小。

再总结一下fullgc产生的途径,一是常规的老年代装满后,二是老年代空间分配担保机制担保不成功。所以,老年代适当调大,但又不能太大,要达到够用就行。

知道了这样宏观的选择后,再来看看什么样的对象和类会被回收。对于对象而言,一个对象可能有另一个对象的引用,有引用得都不是垃圾对象,所以早期jdk采用的是引用计数器,每个对象私有的,记录自己被引用的次数,然后认为次数是0的对象是垃圾,所谓的引用分为强引用,软引用,弱引用和虚引用,前两个用得多,强引用就是平时写的变量引用,后面的都是需要将对象用诸如SoftReference包裹起来。软引用在正常情况下不会被回收,但是在gc时,发现回收不了空间时,就会回收软引用。他的应用场景是做对内存不敏感的高速缓存,就比如浏览器的回退按钮,可以有,但没有也问题不大。弱引用和虚引用基本不用。

但又有问题,可能出现两个对象相互引用,而又没有其他的引用,他其实是垃圾,但没有被收集,造成内存泄漏。所以jdk以后换了策略,以前是找哪些是垃圾,现在是找哪些是有用的。方法是可达性分析算法,去找GCRoot结点,以他们为起点。从这些结点开始往下标记,被引用的都标记为非垃圾对象,剩下的是垃圾对象。线程栈的所有局部变量,方法区的静态变量,本地方法栈的变量都可以做GCRoot根结点,也就是可能用到的所有变量。但是不可到达的对象并非一定会被回收,他还有一次被拯救的机会。那就是finalize方法,如果对象覆盖了finalize方法,那么对象被回收前就会执行这个方法,如果这个方法中将自己赋值给了外部变量,也就是说给对象新增了引用,那么他就完成救赎,不会被回收。

说要哪些对象会被回收,再来说说哪些类会被回收。我只知道类是放在方法区中的,他的实例放在堆区中,实例中有指向类的指针,这也是为什么可以通过实例的getClass方法得到类。当堆中没有实例,也就是没有指向类的指针,则这个类就该被回收,但因为有反射和类加载机制的存在,所以多两道判断,就是该类的Class对象不能被引用,因为由反射的知识可以知道,有了Class后,就能够随时生成一个对象来指向类。同样的有了ClassLoader后,也可以直接生成对象,所以必须上面所说的类实例,Class对象和Classloader都没有,这个类才会被回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值