1 概括
1.1 自动内存管理
给对象分配内存+回收分配给对象的内存。前者涉及内存分配策略;后者涉及gc算法(标记-清除、复制、标记-整理、分代收集)以及gc器(Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1)
1.2 内存分配大致方向
堆上分配(JIT编译后拆散为标量类型并间接地栈上分配)、对象主要分配在新生代的Eden区上(若启动了本地线程分配缓冲,将按线程优先在TLAB上分配)、少数情况直接分配在老年代。
1.3 内存分配策略
对象优先在Eden分配;大对象直接进入老年代;长期存活的对象将进入老年代;动态对象年龄判定;空间分配担保。
2 对象优先在Eden分配
2.1 Eden分配
大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,VM将发起一次Minor GC。
新生代总可用空间:Eden区 + 1个Survivor区的总容量。
2.2 新生代和老年代GC
新生代GC(Minor GC)和老年代GC(Major GC/ Full GC)
- 新生代GC:Minor GC指发生在新生代的垃圾收集动作,Java对象大多是朝生夕灭,Minor GC非常频繁,回收速度快。
- 老年代GC:Major GC指发生在老年代的GC,出现此GC,经常会伴随至少一次Minor GC,一般Major GC比Minor GC慢10倍以上。
3 大对象直接进入老年代
3.1大对象
需要大量连续内存空间的Java对象。如长字符串以及数组。
3.2 大对象问题
经常出现大对象会导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来存储大对象。
3.3 参数
-XX:PretenureSizeThreshold
参数,(只对Serial和ParNew有效)可以使得大于该值的对象直接在老年代分配,目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(新生代采用复制算法进行GC)。
4 长期存活的对象将进入老年代
VM给每个对象定义一个对象年龄计数器:
- 若对象在Eden出生并经过第一次Minor GC后仍然存活,且能被Survivor容纳,将被移动到Survivor空间中,并且对象年龄设为1;
- 对象在Survivor中每“熬过”一次Minor GC,年龄就增加1岁;
- 当年龄增加到一定程度(默认为15岁),将会晋升到老年代中;(老年代进入的阈值可以通过-XX:MaxTenuringThreshold设置)
5 动态对象年龄判定
若在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold的阈值。
6 空间分配担保
(针对老年代是否有能力担保的)
- 在发生Minor GC前,VM会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,若成立,则Minor GC可以确保安全;
- 若不成立,则VM查看HandlePromotionFailure设置值是否允许担保失败,若允许,则继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,若大于,则尝试进行一次Minor GC,若小于,或参数设置为不允许担保失败则改为Full GC。
- JDK6 Update24之后的规则为:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就进行Minor GC,否则进行Full GC。