说明:本文中的JVM限于HotSpot JVM,详细介绍了,对象在哪里创建、创建的步骤是什么、创建之后的对象长什么样、以及想要使用该对象的时候如何访问。
1 对象在哪里创建
这个问题,就是在你买房子之前,要确定在哪里买。排除房产投资什么的,肯定是在你的工作地买啦。
Java 是面向对象的编程语言,那么,对象到底是在哪里创建的呢?
首先明确一点,几乎所有的对象实例和数组都在堆中分配。这里需要了解 JVM 内存的划分,详见个人另一篇博客JVM 内存划分、GC 机制与性能优化
看上面的图,对象优先在Eden中分配,Eden空间不够的时候,会触发一次Minor GC。当需要创建大对象(需要大量连续内存空间的java对象)的时候,则会放入老年代中。如果Major GC之后还是老年代不足,就会直接抛出异常。
2 对象如何创建
这个过程可以概括为:
检查是否进行过类加载——分配内存给新生对象——分配的空间初始化为零值(不包括对象头)——设置对象头——init
听起来太抽象?好吧,换一种表述方式:
检查房地产公司是否合法——确定要买100平米户型——清空房间内原本装修遗留的建材——房子和户主信息匹配登记——房产证下来
下面分别详细阐述:
2.1 检查是否进行过类加载
需要创建一个对象的时候,JVM首先会去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用的类是否已经被加载、解析过。如果没有,必须先执行相应的类加载过程。类似于买房子的时候,先要检查这个房地产公司是不是在工商局注册的合法公司,有没有土地买卖权,购买者的户口在当地符不符合限购令的规定啊等等。
2.2 分配内存给新生对象
类加载检查通过后,为新生对象分配内存。对象需要的内存大小在类加载完成后就可以完全确定。分配内存的过程,类似于你要从这个开发商的楼盘中买个100平米的房子,出了100平米的钱,就得给你100平米房间的钥匙。分配的算法有两种:
内存分配算法 | 适用于 | 算法描述 |
---|---|---|
指针碰撞 | Serial、ParNew收集器 | 堆中内存绝对规整,一边是用过的,一边是未使用的,二者之间的分界点放一个指针。需要分配内存给一个对象的时候,就把指针向空闲方向移动一段与对象大小相等的距离 |
空闲列表 | CMS收集器 | 空闲区域与使用过的区域交错排列,JVM维护一个列表,里面记录着哪块是可用的。分配的时候从表中找出合适大小的空间即可 |
接下来,每天那么多人去看房子,可能出现你刚准备交首付,另一个人也看上了同一个房子要付款,也就是所谓的创建对象时的并发安全问题。这个问题有两种解决方案:
- CAS加失败重试
- 每个线程在堆中预先分配一小块内存(TLAB,本地线程分配缓冲)。哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完要分配新的TLAB时,才同步锁定。
2.3 分配的空间初始化为零值(不包括对象头)
内存分配完成后,虚拟机将该内存空间初始化为零值(不包括对象头)。为什么要进行这样的操作?对象的实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型对应的零值。
这个过程类似于,开发商开发楼盘的时候,首先要把每个房间的墙、门砸好,窗户安好,下水道通了等等。房间里就会遗留下来各种木头、梯子、油漆桶等等,这时候就要先把这些遗留材料清空掉,能保证你从旧房子里搬家具过来就可以直接住。但是对象头绝对不能清空,对象头清空了就没人知道这个房子是不是你的了。
2.4 设置对象头
JVM设置对象的对象头,包括该对象是哪个类的实例,哈希码,GC年龄分代等信息。对象头后面会详细讲。这个过程类似于房子和户主信息匹配登记。
2.5 init
上面的工作之后,对JVM来说,新的对象已经创建好了。但是对于程序员来说,这一切才刚刚开始。执行new指令后,接着执行init
方法,对程序员来说,这个对象才真正可用。
这个过程类似于,开发商给你钥匙,你可以住进去了。但是这个房子真正属于你(拥有买卖权),得等到房产证下来。
3 对象到底长什么样
这里我们需要了解的是你所购买的房屋的结构,几室几厅,厕所在哪里等等。
对象分为三部分:对象头、实例数据、对齐填充
item | 详述 |
---|---|
对象头 | 分为两部分,一是用于存储对象自身的运行时数据,包括哈希码、GC年龄分代、锁标志状态、线程持有的锁、偏向线程ID、偏向时间戳等等,官方称为“Mark Word”。另一部分是类型指针,JVM通过该指针确定该对象是哪个对象的实例 |
实例数据 | 对象的具体内容 |
对齐填充 | 并非必然存在,只是因为JVM中对象大小必须是八字节的整数倍,该部分用来补全 |
注意,普通java对象的元数据信息可以确定该对象的大小,但是数组不可以。因此数组的对象头还应有一块存储数组长度的数据。
4 对象如何被访问
当你创建一个对象之后,为了使用,总得先访问该对象。
首先需要复习一个知识点。Java虚拟机栈中,存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。其中局部变量表存放了三个信息:
- 各种基本数据类型(boolean、byte、char、short、int、float、long、double)
- 对象引用(reference)
- returnAddress地址(指示JVM的指令执行到哪一行)
也就是说,访问的起始点,永远是局部变量表中的对象引用(reference)。
4.1 通过句柄访问
对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改
4.2 通过直接指针访问
速度更快,节省了一次指针定位的时间开销,HotSpot采取的就是这种方式。
原文转自
http://blog.csdn.net/antony9118/article/details/53055368