论述题 7.  Java堆被划分成老年代和年轻代,它们有什么区别?
【正确答案】根据对象的生命周期的长短把对象分成不同的种类(年轻代、年老代和持久代),并分别进行内存回收,也就是分代垃圾回收。
   分代垃圾回收算法的主要思路如下:把堆分成两个或者多个子堆,每一个子堆被视为一代。在运行的过程中,优先收集那些年幼的对象,如果一个对象经过多次收集仍然存活,那么就可以把这个对象转移到高一级的堆里,减少对其的扫描次数。
   目前最常用的JVM是SUN公司(现被Oracle公司收购)的HotSport,它采用的算法为分代回收。
   HotSport把JVM中堆空间划分为三个代:年轻代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)。以下将分别对这三个代进行分析。
   1)年轻代:被分成3个部分,一个Eden区和两个相同的Survivor区。Eden区主要用来存储新建的对象,Survivor区也被叫作from和to区,Survivor区是大小相等的两块区域,在使用“复制”回收算法时,作为双缓存,起内存整理的作用,因此,Survivor区始终都保持一个是空的。
   2)老年代:主要存储生命周期较长的对象、超大的对象(无法在新生代分配的对象)。
   3)永久代:存放代码、字符串常量池和静态变量等可以持久化的数据。SunJDK把方法区实现在了永久代。
   由于永久代基本不参与垃圾回收,所以,这里重点介绍的是年轻代和老年代的垃圾回收方法。
   新建对象优先在Eden区分配内存,如果Eden区已满,在创建对象的时候,会因为无法申请到空间而触发minorGc操作,minorGc主要用来对年轻代垃圾进行回收:把Eden区中不能被回收的对象放入到空的Survivor区,另一个Survivor区里不能被垃圾回收器回收的对象也会被放入到这个Survivor区,这样能保证有一个Survivor区是空的。如果在这个过程中发现Survivor区也满了,就会把这些对象复制到老年代,或者Survivor区并没有满,但是有些对象已经存在非常长的时间,这些对象也将被放到老年代中,如果老年代也被放满了,就会触发fullGC。
   引申:什么情况下会触发fullGC,如何避免?
   fullGC是用来清理整个堆空间,包括年轻代和永久代,所以fullGC会造成很大的系统资源开销。因此,通常需要尽量避免fullGC操作。
   下面介绍几种常见的fullGC产生的原因以及避免的方法。
   1)调用System.gc()方法会触发fullGC,因此,在编码的时候尽量避免调用这个方法。
   2)老年代空间不足。由于老年代主要用来存储从年轻代转入的对象、大对象和大数组,因此,为了避免触发fullGC,应尽量做到让对象在Minor GC阶段被回收,不要创建过大的对象及数组。由于在Minor GC时,只有Survivor区放不下的对象才会被放入老年代,而此时只有老年代也放不下才会触发fullGC,因此,另外一种避免fullGC的方法如下:根据实际情况增大Survivor区、老年代空间或调低触发并发GC(并发垃圾回收)的概率。
   3)永久代满。永久代主要存放class相关的信息,当永久代满的时候,也会触发fullGC。为了避免这种情况的发生,可以增大永久代的空间(例如-XX:MaxPermSize=16m:设置永久代大小为16M)。为了避免永久代满引起的fullGC,也可以开启CMS回收永久代选项(开启的选项为+CMSPermGenSweepingEnabled-XX:+CMSClassUnloadingEnabled。CMS利用和应用程序线程并发的垃圾回收线程来进行垃圾回收操作。
   需要注意的是,Java8中已经移除了永久代,新加了一个称为元数据区的native内存区,所以,大部分类的元数据都在本地内存中分配。
【答案解析】