我们都知道JVM的堆内存分为新生代(Eden + s0+ s1)和老年代
首先 我们来看一下一个方法运行时 堆内存的占用情况
- jps 查看所有运行的项目及方法
- jmap -heap [方法id] 查看堆占用
其他参数配置
- 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ,可以通过参数 –XX:NewRatio 配 置。
- 默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定)
- Survivor区中的对象被复制次数为15(对应虚拟机参数 -XX:+MaxTenuringThreshold)
问题来了
为什么要分为Eden和Survivor?为什么要设置两个Survivor区?
- 如果没有Survivor,Eden区每进行一次轻GC,存活的对象就会被送到老年代。老年代很快被 填满,触发重GC 老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比轻GC 长得多
- Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选 保证,只有经历16次轻 GC还能在新生代中存活的对象,才会被送到老年代。
- 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次轻GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满 了,就再触发一次轻GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续 的内存空间,避免了碎片化的发生)
由上边的问题又引出一个新的问题
JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代
- Java堆 = 老年代 + 新生代
- 新生代 = Eden + S0 + S1 当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对 象,则会转移到 Survivor区。
- 大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态; 如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为 1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存 活的对象进入老年态。
- 老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存 堆 – 包括年轻代和年老代。
- Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10 倍以上。
推荐阅读