java技术体系提倡的自动内存管理最终可以归结为自动化解决了两个问题:给对象分配内存以及回收分配给对象的内存。
下面说一下内存分配,对象内存分配,往大方向上讲就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配,少数情况下可能会直接分配在老年代中, 分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集组合,还有虚拟机中与内存相关的参数的设置。
(1)对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配,当Eden区中没有足够的空间进行分配时,虚拟机将发起一次MinorGC
在执行上面程序之前要在Run -->edit configuration 中配置一下VM Option,添加下面的参数:
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
-Xms初始堆大小
-Xmx最大堆大小
-Xmn 年轻代大小
-XX SurvivorRatio Eden区与Survivor区的大小比值、
-XX PrintGCDetails 输出形式
-verbose:gc表示输出虚拟机中GC的详细情况
在上面程序中,尝试分配3个2MB大小和1个4MB大小的对象,在运行时通过参数-Xms 20M -Xmx20M -Xmn 10M这三个参数分配给老年代。从输出结果
eden space 8192K、from space 1024K、tospace 1024K的信息表明新生代Eden区与Survivor区的空间比例是8:1;
新生代的可用空间为9216KB(Eden区+1个Survivor区的总容量)。
在执行testAllocation()空间中分配allocation4对象的语句会发生一次Minor GC,这次GC的结果是新生代6651KB变为148KB,而总的内存占用量几乎没有变少(因为allocation1、2、3三个对象都是存活的,虚拟机几乎没有找到可回收的对象),这次GC发生原因是给allocation4分配内存时,发现Eden区已经被占用6MB,剩余空间不足以分配allocation4所需的4MB,因此发生MinorGC。GC期间虚拟机又发现现已有的3个2MB大小的对象无法全部放入survior空间(survivor空间只有1MB大小)所以只好通过分配担保机制提前转移到老年代中。
这个GC结束,4MB的allocation4被分配在Eden区,因此结果是Eden占用4MB(被allocation4占用),survivor空闲,老年代被占用6Mb(被allocation1、2 、3占用)。
注解:MinorGc 和FullGc有什么区别?
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕死的特性,所以MinorGc非常频繁,一般回收速度也比较快。
老年代GC(Major GC/Full GC)指发生在老年代的GC,出现了Major GC,经常伴随至少一次的Minor Gc(但并非绝对的,)Major Gc速度一般会比MinorGC 慢10倍以上。
(2)大对象直接进入老年代
所谓大对象就是指,需要大量连续内存空间的java对象,最典型的是很长的字符串以及数组,大对象对虚拟机内存分配来说是一个坏消息,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。
虚拟机中有-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配,这样做的目的是避免在Eden区一级两个Survivor区之间发生大量的内存拷贝。
(3)长期存活的对象将进入老年大
虚拟机采用了分代收集的思想来管理内存,那内存回收时就必须识别哪些对象应当放在新生代,哪些对象应该放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1,对象在Survivor区中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁)时,就会被晋升到老年代中。
(4)动态对象年龄判定
为了更好的适应不同程序的内存状况,虚拟机并不总是必须达到MaxTenuringThreashold(VM中设置-XX:MaxTenURingThreashold = 15或者其他数值来设定年龄增加到几进入老年代中)才能晋升到老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
(5)空间分配担保
在发生MinorGC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间,如果大于,则改为直接进行一次FullGC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行MinorGC,如果不允许,则也要改为进行一次Full GC。
新生代只使用其中一个Survivor空间作为轮换备份,当出现大量对象在MinorGc后仍然存活的情况时(最极端就是内存回收后新生代中的所有对象都存活),就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代。老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,只好取前一次回收晋升到老年代对象容量的平均值大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行FullGc让老年代腾出更多空间。