对象的内存分配,一般就是在堆上分配空间,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB(缓存)上分配。也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。
1、对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
-xmn10M 为jvm的新生代空间;
-Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -Xloggc:D:/Documents/gc.log
public static void main(String...s) throws Exception{ byte[] b1 = new byte[2 * 1024*1024]; byte[] b2 = new byte[2 * 1024*1024]; byte[] b3 = new byte[2 * 1024*1024]; byte[] b4 = new byte[4 * 1024*1024];//超过了10m }没有出现异常,查看gc日志:
0.082: [GC 0.082: [DefNew: 6692K->370K(9216K), 0.0028794 secs] 6692K->6514K(19456K), 0.0029189 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4794K [0x334f0000, 0x33ef0000, 0x33ef0000) eden space 8192K, 54% used [0x334f0000, 0x33941fa0, 0x33cf0000) from space 1024K, 36% used [0x33df0000, 0x33e4cb30, 0x33ef0000) to space 1024K, 0% used [0x33cf0000, 0x33cf0000, 0x33df0000) tenured generation total 10240K, used 6144K [0x33ef0000, 0x348f0000, 0x348f0000) the space 10240K, 60% used [0x33ef0000, 0x344f0030, 0x344f0200, 0x348f0000) compacting perm gen total 12288K, used 146K [0x348f0000, 0x354f0000, 0x388f0000) the space 12288K, 1% used [0x348f0000, 0x34914a68, 0x34914c00, 0x354f0000) ro space 10240K, 45% used [0x388f0000, 0x38d77290, 0x38d77400, 0x392f0000) rw space 12288K, 54% used [0x392f0000, 0x3997ace8, 0x3997ae00, 0x39ef0000)
分析:
new generation(新生代):
eden space 8192K = 10*1024kb *80%
form space(survival) = 1024kb ****这里可用的空间大小为9*1024kb****
to space(survival) = 1024kb
tenured generation(老年代):
space 10* 1024kb
执行main方法时,会发生minor gc 因为新生代需要的内存空间为10m而可用的空间是9m,而b1,b2,b3大小为2m比to space(1m)大,不能通过复制方法执行;此时gc只能通过担保机制将b1,b2,b3放入tenured generation中,这样新生代可用空间又为9m了;
1.the space 10240K, 60% used
2.eden space 8192K, 54% used
from space 1024K, 36% used
2、大对象直接进入老年代
大对象就是指需要大量连续内存空间的Java对象,典型大对象:长字符串及数组。大对象对虚拟机的内存分配来说就是一个坏消息(遇到一群“朝生夕灭”的“短命大对象”,写程序时应当避免),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。
虚拟机提供了一个-XX:PretenureSizeThreshold=3*1024*1024参数,令大于这个设置值的对象直接在老年代中分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存拷贝
PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效,Parallel Scavenge收集器不认识这个参数,Parallel Scavenge收集器一般并不需要设置。如果遇到必须使用此参数的场合,可以考虑ParNew加CMS的收集器组合。
3、长期存活的对象将进入老年代
内存回收时应该识别哪些对象应当放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold=15来设置。
4、 动态对象年龄判定
为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
5、空间分配担保
在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,则也要改为进行一次Full GC。大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁。