1 堆分区
1.1 新生代(Young Generation)和老年代(Tenured Generation)
JVM根据对象生存的特点,将对象划分为新生代和老年代。大部分对象在JVM内存中处是新生代,他们有一个特点,“朝生夕死”,即他们的生命周期很短,伴随着一次GC可能就会被回收掉。相对于新生代来说,老年代就是相对“稳定”的一些对象。他们被JVM“认为”是可以长期存活的对象。一般来说,晋级为老年代的对象都是经过了一定次数GC存活下来的对象,当然也有可能是直接分配在老年代中的对象。
1.2 EdenSurvivor
在垃圾回收算法中,有一个算法称为复制算法。其基本思想是把内存划分为相等大小的两个部分,内存分配仅在其中一个区域进行,当发生GC时,把一个区域中存活的对象复制到另外一个区域中,然后对这个区域进行整个GC,这样就保证了内存连续性,减少了内存碎片的可能。然而这种方式却牺牲了一半的内存空间。因此就出现了Eden区与Survivor区的概念。
他们是针对新生对象会很快消亡的特点,把新生代区域划分为三个空间一个较大的Eden区,和两个相等的大小的From Survivor和To Survivor区。他们的默认大小比值为8:1,即如果新生代大小为10MB的话,Eden区大小为8MB,from和to的空间各为1MB。每次内存分配的时候,在Eden和一个1个survivor区域中分配(9MB大小),GC时把存活的对象复制到另一个survivor区域中,清理之前的9MB空间。当survivor空间不足时,启动老年代内存担保。
2 内存分配与回收策略
2.1对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。对于这种情况,也有特例,当遇到大对象的时候,就要在老年代中分配。所谓的大对象指的是那种很长的字符串以及数组。大对象的分配对虚拟机来说是一个坏消息,经常出现大对象容易导致内存还有不少空间而不得不触发一次垃圾收集来获取连续的空间“安置”他们。
虚拟机提供了一个参数–XX:PertenureSizeThreshold,令大于这个设置值的对象直接在老年代
中分配,这样就避免了在Eden区及两个Survivor 区中发生大量的内存复制。2.2 年龄增加与升级
虚拟机采用了分代思想去管理对象,那就应该识别出来哪些对象放在新生代,哪些对象放在老年代。为了做到这点,虚拟机为每个对象设置了一个Age计数器,如果对象在一个MinorGC过后再仍然存活,就将被移到survivor区中,然后age+1。对象每经历一次Minor GC age就会增加1岁,当达到某个阈值的时候(默认为15岁),就会晋升到老年代。可以通过设置参数-XX:MaxTenuringThreshold来设置晋升的年龄。
然而对于虚拟机来说,并不是一定要达到这个要求才能晋升为老年代。还有一种情况就是某个年龄的对象大小的总和大于survivor区的一半时,年龄大于或等于这个年龄的对象都将进入到老年区。2.3 内存分配担保
在MinorGC之前,虚拟机会检验老年代最大可用连续空间是否大于新生代所有对象的总和,如果这个条件成立,那么MinorGC是安全的,这是一种基于最坏情况的考虑,就是新生代所有的对象都存活下来进入到老年代中。
如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会检查老年代最大可用连续空间是否大于历次晋升老年代的对象大小的平均值。如果大于,则尝试进行一次MinorGC,如果小于或者HandlePromotionFailure设置不允许,就要进行一次FullGC。
这里需要解释一下为什么要这么做,复制算法为了提高内存使用率,只有一个survivor区域作为轮换备份,如果出现大量对象经过一次MinorGC以后仍然存活的情况,survivor空间不够就要申请老年代的内存担保。这个担保是可能失败的,因为老年代中也许没有足够的空间。然后这种检测又发生在MinorGC之前,这个过程是无法知道有多少对象需要进入老年代的,所以就取一个历史平均值。
3 方法区的回收
对于方法区,我们会成为“永久代”,因为虚拟机规范中要求这个区域可以不发生垃圾回收,而且这个与堆中相比,这个区域的回收的性价比也不那么高。远远低于堆的70~95%。
永久代的垃圾收集主要两个部分内容:废弃常量和无用的类。
3.1回收常量
回收废弃常量与Java堆中的收集对象很相似。以常量池字面值为例,常量池中有字符串abc,但是当前系统中没有一个String类型的变量叫abc,这个abc可以被认为是垃圾了。这个时候如果发生垃圾回收,就会把abc从常量池中清理出去
3.2 回收类
相对于无用常量的判断,对于无用的类型的判断就要稍微复杂一点。类需要同时满足3个条件才能被判定为”无用的类”。
(1)该类的所有实例都已经被回收,也就是Java堆中不包括任何一个该类的实例
(2)加载该类的classloader已经被回收
(3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
满足以上三个条件,只能说该类可能被回收,而并不是没用了就一定被回收。在大量使用反射、动态代理、CGlib等字节码框架、动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。