介绍
- Java是一种面向对象的语言,而Java对象在JVM中存储是由一定结构的。而这个 Java对象自身的存储模型称之为Java对象模型
- HotSpot虚拟机中,设计了一个OOP-Klass Model.OOP指的是普通对象指针,而Klass用来描述对象的具体类型。
- 如下图所示是一个简单的Java对象的OOP-Klass模型,也就是Java对象模型
Java对象创建过程
- 类加载检查:虚拟机遇到new字节码指令的时候,首先去检查这个指令的参数是够可以在常量池中找到对象的符号引用,并且检查这个符号引用是否已经被加载过、解析、和初始化过,如果没有,则必须先要执行相应的类加载过程
- 分配内存:在类加载检查完后,接下来虚拟机会为对象分配内存。对象所需的大小在类加载完成后便可确定,为对象分配空间的任务相当于在堆中开辟出一块确定的大小,分配方式有指针碰撞、空闲列表两种,具体使用哪种还需要看Java堆的规整程度,是够规整取决于使用的何中垃圾回收算法,如果使用标记压缩则使用指针碰撞,标记清除则使用空闲列表
- 初始化零值:内存分配完成后,虚拟机要对对象完成必要的设置,例如这个对象是哪个类的实例,如果才能找到类的元数据信息、对象的哈希码、GC分代年龄等信息都是存放在对象头中的。另外根据虚拟机当前的运行状态不同,来看是否生成偏向锁(锁的标志在对象头中)对象头有不同的设置方式
- 执行init方法:上面几步完成之后对于虚拟机来说对象已经创建完成了,但是对于Java程序来说还需要执行init方法来完成相关变量的初始化
并发内存分配问题
在创建对象的时候有一个非常重要的问题就是线程安全问题,在实际开发过程中,对象创建是非常频繁的,作为虚拟机而言必须要保证线程是安全的,通常来讲有两种方式来保证线程安全
- CAS+失败重试:CAS乐观锁是一种实现方式,每次不进行加锁,认为不会产生冲突,产生冲突就不断地重试,直到成功为止
- TLAB:为每个线程在Eden区分配一块固定内存,JVM在给线程中的对象分配内存的时候首先将对象分配在TLAB中,当对象的大小大于TLAB或者tlab空间用完了则使用CAS+失败重试
Java对象的访问定位方式
Java是通过引用的方式来操作堆上的对象的,那么如何通过引用定位、访问对象的具体位置。对象的访问方式是由虚拟机来决定的,主流的有以下两种
- 句柄池:简单来说就是在堆中开辟出一块空间作为句柄池,引用中存储对象的句柄地址,句柄中包含对象的实例数据、类型数据的地址信息。优点是引用中稳定的存储地址信息,在对象被移动的时候只需要改变句柄中实例数据的指针就可以了,不需要改变引用本身
- 直接指针:与句柄池不同,ref引用中直接存储的就是对象的实例数据,但是对象的类型还是和句柄池的方式相同。优点很明显那就是速度快,和句柄池相比省下了一次指针定位的时间开销
- 内存溢出有两种:StackOverFlowError:当请求的栈深度大于虚拟机所允许的最大深度、OutOfMemoryError:虚拟机在扩展栈时无法申请到足够的内存空间[一般都能设置扩大]