java对象内存分配

java对象内存分配

众所周知,java对象对象创建分配的地方在堆上,但是jdk8,java对象可以分配在栈上。
在栈上为对象分配内存具有一定的条件性,即在栈上对象的生命周期可以随着方法移除栈而死亡才能将对象放入到栈上。

对象在栈中

如下例子,test2方法调入栈之后,在方法中创建了局部变量user,user的作用域在方法test2上,方法调用结束之后,对象user就成为了垃圾对象。如果对象user方法堆上,user对象的释放需要等到minor gc进行垃圾回收才可以做到。但是如果将对象放大栈上,对象就会随着方法执行结束而消亡。

 public void test2() { 
    User user = new User(); 
    user.setId(1); 
    user.setName("zhuge"); 
    //TODO 保存到数据库 
}

通过上面的分析,我们可以得出一个结论,对象在栈上创建,可以很好的减少jvm垃圾回收的工作,这样可以提高堆空间的使用率。但是不是所有的对象都能放到栈上,只能满足了以上条件才可以将对象放到栈上。

对象在栈上需要注意的事项

JVM 会判断对象分配到栈上的逻辑,这种JVM分析的方式叫做对象分析逃逸。这种方法可以很好的减少堆内存的GC压力。

对象分析逃逸有一定的优点:减少gc的压力。但是对象占用的空间一般较大,在栈上分配会遇到没有连续的内存空间来支持对象的存放,JVM为了解决这种栈空间不连续问题,提出了标量替换的思路,即使用链表的方式,对零散的内存空间进行组织,保证对象可以放在栈上。跟标量替换相对应的概念叫做标量与聚合量,它是一种不可进一步分解的量。

· 注意:开启标量替换参数(-XX:+EliminateAllocations),JDK7之后默认 开启。JDK7之后默认开启逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)。

对象在堆上分配

对象在堆上分配大多数情况下,对象都在eden区分配,这是因为在eden区产生的对象,大多数对象可以及时的被垃圾回收器回收。

首先我们区分一下minor gc/yong gc 和 full gc/ major gc的区别
minor gc 只要发生在年轻代上的一种垃圾回收机制,当eden区空间占满之后,会触发minor gc 将eden区的数据放到 survivor,当达到一定年龄之后,会将年轻代上的对象放到老年代。
full gc 主要发生在老年代上,当老年代空间占满后会触发full gc 清理老年代上存放的对象信息。

Eden与Survivor区默认8:1:1

大量的对象创建后会放到eden区,当minor gc 之后eden上99%的对象会被回收掉。剩余存活 的对象会被挪到为空的那块survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor区垃圾对象回 收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代的对象都是朝生夕死的,存活时间很短,所 以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可,JVM默认有这个参数-XX:+UseAdaptiveSizePolicy(默认开启),会导致这个8:1:1比例自动变化,如果不想这个比例有变 化可以设置参数-XX:-UseAdaptiveSizePolicy。

大对象在老年代生成

大家一起思考一个问题。在堆上一个很大内存需要分配空间,并且这个对象长期存活。如果将大对象放到eden区之后,eden区满之后会触发minor gc ,这个时候Survivor区剩余空间不足以存放大对象。JVM会启动full gc 将大对象存放到老年代上。
以上的问题,一个大的对象放到eden区会额外造成多次的垃圾回收,造成对象不停的挪动位置。
为了解决这种问题,我们可以将大的对象直接放到老年代,因为一个大对象放到eden区,发生minor gc对象还是会放到老年代。提前将对象放入老年代可以很好的减少jvm的垃圾回收。
jvm可以配置指定大对象的标识,指定对象的大小,当对象达到一定的大小之后可以直接进入老年代。比如设置JVM参数:-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC ,再执行下上面的第一 个程序会发现大对象直接进了老年代。

长期存活的对象将进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在 老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度 (默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代 的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

对象动态年龄判断

当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的 50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了, 例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会 把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年 龄判断机制一般是在minor gc之后触发的。

老年代空间分配担保机制

年轻代每次minorgc之前JVM都会计算下老年代剩余可用空间 如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象) 就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了 如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。 如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾, 如果回收完还是没有足够空间存放新的对象就会发生"OOM" 当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”

你可能感兴趣的:(java,jvm,开发语言)