分代算法
背景
JVM中堆是存放java对象的地方,也是内存管理的核心对象。
前面在垃圾收集器中已经介绍了GC会带来2个问题,“碎片问题”和“效率问题”,为了解决这两个问题,人们不断的再优化垃圾回收算法,已经介绍了常用的引用计数、标记清除、复制、标记整理。
现在让我们来研究JVM当前采用的最优算法 ,分代算法。
理论
首先引入了java对象年龄的概念,每次GC操作作为一年,存活下来的对象年龄都+1;
然后对不同年龄段的对象进行分区管理,分为新生代和老年代两个区,新生代又分为两部分Eden区和Survival区,Survival区又分为From区和To区;
最后针对新生代和老年代采用不同的垃圾回收策略。
为什么要引入对象年龄和分代概念呢?
因为大部分java对象都是“朝生夕死”,频繁的创建和回收,根据“28定律”可以得出只有20%的对象是需要长期存活的。为了提升GC效率,没必要对整个堆进行频繁的垃圾回收。
所以先将堆分成两大部分,根据对象年龄来决定对象保存的区域,对象成年标准就是分代的标准,比如设定为16岁,大于16岁进入老年代。
分代算法的具体操作流程
- 新生代中创建Eden区(伊甸园),所有对象从这里出生;
- 每次GC后,Eden区和From区中幸存的对象将进入To区,保证Eden和From每次都清空,不存在碎片问题;
- 每次GC后,To区中的幸存的对象如果成年了将进入老年代;
- To区中的幸存的对象如果未成年,这样造成To区产生碎片,为了解决这个问题,GC后都将From和To的标识互换,这样下次GC时,又可以重复步骤2;
垃圾收集器分类
结合垃圾回收算法和堆的分代模型,可以得到新生代和老年代对应的垃圾收集器。
新生代:
- 串行收集器Serial、ParNew、
- 并发收集器Parallel
- 理想收集器G1
老年代:
- 并发收集器CMS
- 串行收集器ParNew Old、
- 并发收集器Parallel Old
- 理想收集器G1
垃圾收集触发时机
从对象在堆的分代区流动的过程,可以清晰的看出对象都是从Eden创建的,当对象创建时内存不够了,肯定就会触发垃圾回收。
既然堆已经分代,新生代和老年代的GC触发频率肯定是不一样的,又可以将GC分类为Minar GC(新生代)、Major GC(老年代)、Full GC(新生代+老年代+持久带+其他)。根据对象区域流动过程,那个区域满了,就会触发对应区域的GC。
创建对象失败,首先触发Minar GC,如果不能解决问题,再触发 Major GC,如果还是不行就要触发Full GC。
除了创建对象失败之外,系统还会定时自动进行Minar GC,还可以手动 触发System.gc()。
总结触发GC时机
- 创建对象失败;
- 手动触发;
持久带
由于JAVA8之后将方法区直接改成了本地内存机制,彻底解决方法区溢出问题,就不再需要考虑持久带的内存管理问题。