对象内存分配流程图
验证对象是否栈上分配
/**
* 栈上分配,标量替换
* 代码调用了1亿次alloc(),如果是分配到堆上,大概需要1GB以上堆空间,如果堆空间小于该值,必然会触发GC。
*
* 使用如下参数不会发生GC
* -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
* 使用如下参数都会发生大量GC
* -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
* -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
*/
public class AllotOnStack {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
alloc();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
private static void alloc() {
User user = new User();
user.setNum(1);
user.setUuid("lcf");
}
}
对象在堆内存的分配的情况
通过下图可以看到类在运行过程中,会将对象存在eden区,eden区在内存未满的情况下,并不会进行minor gc。图中eden区内存占用了87%
下图中可以看到,在eden区未满的情况下,新进来的对选哪个会持续向eden区加入对象。
下图因为eden区满了之后,Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC,GC期间虚拟机又发现allocation2无法存入Survior空间,所以只好把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation2,所以不会出现FullGC。
下图执行MinorGC后,后面分配的对象如果能够存在eden区的话,还是会在eden区分配内存。 将存活的对象分别移动到survivor和老年代。
下图通过设置jvm参数-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC,可以使大于1000000 字节的对象直接进入老年代。
好处是可以使存活在年期带的大对象不需要在eden和survivor来回移动进行gc,从而消耗性能。