背景摘要:前两篇文章主要提到过JVM内存结构、JVM对象创建过程及空间开辟。JVM内存结构篇中我们了解到了对象一般是存放于堆中,那么其实栈也是可以存放对象的,这就是基于我们的JVM栈上分配了。至于JVM对象创建篇,我们了解了对象的创建详细过程步骤,以及JVM是如何为对象分配空间,那么这里也继续分析JVM的对象组成部分以及对象引用方式。
目录
一、JVM栈上分配
我们在之前了解过对象一般存放于堆中,那么其实栈也是可以存放对象的,但是一般都是栈帧里面的对象,也就是不会给外部方法共享使用,内部使用后就结束。在JDK 1.6.25之后,可以进行栈上分配。也就是这个原理。
1.1、栈上分配
如果被调用方法的new对象,是栈帧(即方法)中所私有的,无需共享,那么就无需存放在堆中,可存入栈。即栈上分配。
1.2、内存逃逸
比如此处伪代码,理论上会执行栈上分配的new对象,但需要返回给外部方法使用,就不能存在于栈帧中,就只能放入堆中共享,这种行为就叫做内存逃逸。
public Object a(){
return new Object();
}
public static void main(String[] args){
Object o = a();
o.toString();
}
注:重点在于其他地方是否需要引用。如果被使用绝对不能被栈帧私有化,只能分配在堆中,所以发生内存逃逸。
当然如果对象太大,也只能在堆上分配。
一句话:理论上应被栈上分配的对象,由于外部引用或对象太大无法执行,从而分配在堆中,即内存逃逸。
PS:来自逃出来的对象:
二、JVM对象组成部分
JVM对象包含对象头、对象实例数据、数据填充。
当然此对象非彼对象
1.对象头
哈希值: gc分代年龄、锁状态标志、线程持有的锁、线程当前ID
类型指针:当前对象指向那个class对象,对应调用的方法也是调用class对象中的方法
数组长度:只有数组才会有这个值
2.对象实例数据
主要存放自身的 属性变量,包括父类属性等。
3.数据填充
使用数据填充,没有实际的意义 HotStop 虚拟机指定对象大小必须是8个字节的整数倍。如果不是8个字节则使用此进行填充
三、对象引用方式
栈里面的变量可以引用到堆里的对象,至于怎么去引用的,Java没说。
但是其实就是有两种方法引用:
1、直接引用 速度较快,但是对象改变的时候,栈的指向也要改变
2、句柄引用 速度慢,有两层,但是对象改变时,栈的指向无需改变,堆内改变引用即可。
不同的虚拟机不一样,oracle是直接引用。都是虚拟机自动操作。
原理图如下:
总结
JVM栈上分配与内存逃逸:
在方法体内的变量理论上应被栈上分配的对象由于外部引用或对象太大无法执行,从而分配在堆中,即内存逃逸。
对象组成部分:
首先是对象头。哈希值,存放对象当前线程信息,类型指针(当前对象指向那个class对象)等,并且数组包含长度值。
对象实例包含自身属性变量与父类属性、最后数据填充是为了虚拟机机制而准备,必须是8的整数倍。
对象引用方式:
直接引用效率高,但对象改变时需要更新引用地址。句柄引用效率较低,但对象改变无需重定向,堆内部处理即可。
注:堆的值更新是因为垃圾回收算法中的复制算法。把当前空间的对象删掉在其他空间创建。固更改了对象地址。