JVM是如何给对象分配内存的?
在前文《Java对象是如何创建出来的?》中我们知道,给对象分配内存是Java对象创建过程中的一个环节,至于如何给对象分配内存,在文中并未详细描述,下面,我给大家解答这个疑问。
在《 JVM概念及体系结构》一文中,我们了解到JVM的体系结构,知道运行时数据区是JVM中的内存区域,它用于存储Java程序运行时的数据,包括方法区、堆、栈、程序计数器等。对象属于Java程序运行时的数据,那么给对象分配内存也自然是在运行时数据区发生的事情。下面通过一个流程图描述出来:
什么是逃逸分析?
逃逸分析就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中。开启逃逸分析参数(-XX:+DoEscapeAnalysis),JDK7之后默认开启逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)。
示例:
public A get() {
A a = new A();
a.setId(1);
a.setName("A Object");
return a;
}
public void handle() {
A a = new A();
a.setId(1);
a.setName("A Object");
}
示例中,get()方法中的a对象被返回了,这个对象的作用域范围不确定,handle()方法中a对象我们可以确定当方法结束时,a对象也就认为是无效对象,没有任何地方引用它,对于这样的对象,其实是可以分配在栈内存里,让它在方法结束时跟随栈内存一起回收掉,而不要等着垃圾回收才能将它回收掉。
什么是标量替换?
标量替换就是通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就不会因为没有一大块连续空间导致对象内存不够分配。开启标量替换参数(-XX:+EliminateAllocations),JDK7之后默认开启。
什么是老年代?什么是Eden区?
目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代;
新生代又被划分成Eden 空间、 From Survivor 和 To Survivor三块区域。Eden : From Survivor : To Survivor 空间默认大小设成8:1:1,对象总是在Eden区出生,From Survivor 保存当前的幸存对象,To Survivor 为空。当Eden区空间被占满后,会触发MinorGC,当MinorGC发生后,Eden 区活着的对象和From Survivor区存储的对象会被复制到To Survivor区;因为新生代的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适的,让Eden区尽量的大,From Survivor 和 To Survivor 区够用即可,JVM默认有这个参数-XX:+UseAdaptiveSizePolicy(默认开启),会导致这个8:1:1比例自动变化,如果不想这个比例有变化可以设置参数-XX:-UseAdaptiveSizePolicy。
老年代主要用来存放生命周期更长的,或者占用内存比较大的对象,变化的频率没有那么频繁。MajorGC不会频繁执行,新生代中的对象满足一定条件后,会转移到老年代内存中。