目录
一、堆内部结构
Java JDK7及以前逻辑上被分为三部分:新生代、老年代、永久代 。
Java JDK8及以后被分为了:新生代、老年代、元空间;元空间存储的对象与永久代相同,区别是:元空间并不在jvm中,使用的是本地内存。
为什么要移除永久代?为融合HotSpot JVM与JRockit VM而做出的改变,因为JRockit没有永久代。
二、为什么进行分代
将生命周期很短的对象放在新生代,将生命周期很长的对象放在老年代,因为在每次GC时,垃圾回收器都会去判断当前对象是否可以被回收,而这些生命周期很长的对象每次都被垃圾回收器扫描,但每次都不回收,故而可以将这些对象放在老年代,并减少对老年代的GC次数,从而将GC的重心放在新生代上。
通过这两个区域对象的生命周期不同,也可以设置不同的垃圾回收算法,比如对新生代中的对象采用复制算法,因为该区域的对象生命周期短,消亡快,所以当发生GC时并不会存在太多存活的对象,而对老年代则采用标记-清除算法。
精简解释:将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及GC频率;针对分类进行不同的垃圾回收算法,对算法扬长避短。
三、为什么要将幸存者区分为两个部分?
主要为了解决碎片化,因为回收一部分对象后,剩余对象占用的内存不连续,也就是碎片化,过于严重的话,当前连续的内存不够新对象存放就会触发GC,这样会提高GC的次数,降低性能,当S0 GC后存活对象转移到S1后存活对象占用的就是连续的内存。
比如一开始只有Eden区和From中有对象,To中是空的。此时进行一次GC操作,From区中对象的年龄就会+1,我们知道Eden区中所有存活的对象会被复制到To区,From区中还能存活的对象会有两个去处;若对象年龄达到默认年龄为15岁,此时对象会被移动到Old区, 如果Eden区和From区 没有达到阈值的对象会被复制到To区。此时Eden区和From区已经被清空。
这时候From和To交换角色,之前的From变成了To,之前的To变成了From。无论如何都要保证To的Survivor区域是空的。
四、对象分配过程
- 创建的对象首先存放在Eden区;
- 当Eden区的空间满了以后,此时创建对象便会触发GC(Minor GC),将Eden区中存活的对象放入幸存区,然后清除Eden区;
- 当触发Eden区的GC时,会将Eden区中还存活的对象放入幸存区S0;
- 当Eden区满了再次触发GC时,会将Eden区中存活的对象和幸存者S0中仍然存活的对象放入幸存区S1;
- 若再一次触发Eden区的GC,则将存活的对象又重新放回幸存区S0,依次循环;
- 存活的对象被放入幸存区一次,年龄就会加1,当对象的年龄到达15岁时,该对象就会被晋升到老年代;
- 只有Eden区满时才会触发GC,此时垃圾回收器会对Eden区和Survivor区进行清理,Survivor区满并不会触发GC,而且GC完成后,Eden区是一个空的状态;
- 当要创建的对象内存超过Eden区空间时,该对象会被直接晋升到老年代;
- 若是老年代仍然放不下,则触发一次在老年代的GC(Full GC);
- 如果GC完成后还是放不下,则出现 OutOfMemoryError: Java heap space 错误。