堆内存分为三个部分:新生代,老年代以及持久代(或者新生区养老区永久储存区等等说法)。
新生代:
其中新生代又分为三个区域: Eden区,幸存区From以及幸存区To。
- Eden区:当对象被创建时,会进入Eden区。
- 幸存区To和From:每当触发一次轻量级GC回收,此时会扫描Eden区和幸存From区,如果对象仍然存活,则将会进入到幸存To区,而死亡的对象则会被垃圾清除。每一次垃圾回收后,会清空Eden区和 From区,并且将From和TO区交换角色。
老年代:
主要存放JVM认为生命周期较长的对象。垃圾回收频率也比较低。
经过多次(默认15次)轻GC依然幸存的对象会被传递到老年代。
持久代
存放类定义,字节码和常量等很少变更的信息。
垃圾回收算法
在进行垃圾回收时JVM会根据不同的堆内存和结构区选择更合适的算法来提高垃圾回收的效率,没有最好的算法,只有最合适的算法,主要的垃圾回收算法有:
1. 引用计数法
原理:给对象中每一个对象分配一个计数器,每当有一次引用则计数器加一,当引用失效则减一,只要当计数器为0时就被垃圾回收。
2. 复制算法
此垃圾回收算法运用在新生代中中,上述提到了每当触发一次轻量级GC回收,此时会扫描Eden区和幸存From区,如果对象仍然存活,则将会复制到幸存To区,并且每次对象年龄会被加一(到达一定年龄会被送进老年代),而Eden和From区的对象则会被垃圾清除。 并且From与To区发生身份的转变,下一次GC回收时会扫面Eden和原本的To区(此时已是From区)。 总之To区永远是上一轮垃圾回收被清空的幸存区域。
缺点:
- 永远需要一个空的内存作为To区占用部分空间。
- 如果对象的存活率很高,需要复制的对象太多,这时候效率就大大降低了。
优点:
- 没有标记和清除的过程,效率高。
- 因为是直接对对象进行复制的,所以不会产生内存碎片
3. 标记清除算法
为了解决复制算法占用空间的缺点,标记清除算法采用标记的方式,一次标记清楚算法需要扫描两次。
-
第一次对扫描区域对活着的对象进行标记。
-
第二次对扫描区域没有标记的对象进行清除。
优点
- 不需要像复制算法那样需要多一个空白的To幸存区,可以节约内存。
缺点
-
第二次扫描未被标记的对象被清楚后,会导致内存空间的存放不连续,存在内存碎片,查找的效率也会降低。
-
需要扫描两次,效率低,在进行GC时,需要停止整个程序。
4. 标记压缩算法
比标记清楚算法多了一步,也就是将内存碎片进行整理,使得空间变得连续。优点明显就是无内存碎片,但是效率更低。
几种垃圾回收算法在JVM中的使用
首先对以上几种算法在内存效率,内存整齐度,内存利用率三个方面排行
内存效率(时间复杂度): 复制算法 > 标记清除 > 标记压缩
内存整齐度: 标记压缩 = 复制 > 标记清除
内存利用率: 标记压缩 = 标记清除 > 复制
新生代: 复制算法
因为新生代中的对象存活率低,需要复制的对象比较少,并且新生代垃圾回收频繁,复制算法的效率最高,所以更合适。
老年代 标记清除与标记压缩混合搭配使用
存活率高,复制算法被PASS, 具体JVM调优就是调整如何更好的搭配使用以达到最优。
有关年轻代的JVM参数
1)-XX:NewSize和-XX:MaxNewSize
用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。
2)-XX:SurvivorRatio
用于设置Eden和其中一个Survivor的比值,这个值也比较重要。
3)-XX:+PrintTenuringDistribution
这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。
4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。