Java虚拟机-对象的创建和访问
一、对象的创建:
创建对象在java上面是很简单的,使用new关键字就可以了,但是其实在虚拟机中,java对象的创建是一个复杂的过程。
当java虚拟机遇到一个new的指令的时候,对象创建的程序正式启动:
1、检查这个指定的参数是否能在常量池当中定位到一个类的符号引用,并且去检查这个符号引用代表的类是否已经被加载、解析和初始化过,如果没有就需要先执行相应的类加载的过程;
2、类加载完成之后需要进行为新生的对象分配内存,对象所需要的内存大小在类加载完成之后就已经完全确定:
分配内存的其中两种方式:
(1)、指针碰撞:
前提:Java堆中的内存时绝对规整的,所有用过的内存放在一边,空闲的放在另外一边,中间有一个指针作为分界点的指示器;
分配方式:把指针往空闲内存那边移动一段与对象所需内存大小相等的距离。
(2)、空闲列表:
前提:Java堆中的内存并不是规整的,已使用和未使用的内存相互交错(这个是正常的,因为GC会进行垃圾回收,中间出现空闲的内存空间原因是这部分的对象被回收了)
分配方式:虚拟机必须维护一个列表,记录那些内存块是可用的,在分配对象内存的时候从列表中找到一块足够大的空间划分给对象实例。
* 使用哪一种方式分配内存,由java的垃圾收集器是否带有压缩整理功能决定,如果有该功能则垃圾收集完成之后java内存会是规整的,可以使用指针碰转法。
注意点:这里是需要考虑到并发的,即使是移动指针这样子的小动作都是有可能产生并发。解决方式有两种:
(1)、堆是线程共享的,每一个线程都可以来申请堆内存空间,对内存空间的分配采用同步处理的方式;
(2)、为每一个线程都分配一小块内存,称为本地线程分配缓冲(TLAB),各个线程需要分配内存的时候就在各自的本地线程分配缓冲(TLAB)上面分配,只有当本地线程分配缓冲(TLAB)的内存空间不足进行重新分配的时候才进行同步锁定。
3、初始化:当内存空间分配完成后需要将内存空间都初始化为零(不含对象头),如果使用的是本地线程分配缓冲(TALB),这一步也可以提前至TLAB分配内存空间的时候进行。这一步操作的目的是保证对象的实例字段在java的代码中可以不赋初始值就直接使用,所以在java程序中,类当中的字段都是会有默认值的。
4、设置对象头:包括对象的类信息元数据、哈希码、GC分代年龄等信息。
5、将对象引用入栈。
至此,在java虚拟机中,一个对象的创建已经完成了。这就是new在java虚拟机当中需要执行的动作。

--------------------------------------我是对象创建的分割线--------------------------------------
二、对象的内存布局:

如图所示,一个对象在内存中的数据包括三个部分:对象头、实例数据、对齐填充。
1、对象头:
对象头的数据包括两个部分:
(1)、对象运行时的数据:如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度在32位和64位虚拟机中的大小分别为32bit和64bit(这部分数据是与对象自身定义的数据无关的额外存储,所以这部分数据被设计成一个无固定的数据结构,即不固定安排大小,因此可以存储更多的数据)。
(2)、对象的类型指针,指向该对象的类元数据。
虚拟机通过类型指针来确定那个对象是类的实例,但是并不是所有的虚拟机实现都是需要在对象数据上保留类型指针的,因为查找对象的类元数据信息不一定要经过对象本身。这个涉及到访问对象的方式(句柄方位方式和直接指针方式,直接指针方式才需要把对象的类型数据指针放在对象头当中)
(3)、如果对象是一个数组,那么在对象头当中还需要有一块用来记录数组的长度,因为虚拟机可以通过普通的java对象的元数据知道java对象的大小,但是无法从数组的元数据当中确定数组的大小。
2、实例数据:
对象的实例数据是对象真正存储的有效信息,也是我们在代码中真正定义的字段内容。对象的实例数据不论是从父类那边继承下来的还是自己创建的,都需要记录。所以我们在编写代码的时候,使用继承让我们少些了很多代码,但是事实上我们编写的子类的对象,字段和数据虽然是可以继承自父类的,但是却是独立的。
3、对齐填充:
这部分的数据是没有意义的,并且也不是必需的,作用是占位。
原因:如果JVM的内存自动管理系统是要求对象的其实地址必需是8字节的整数倍,也就是说对象的大小必需是8字节的整数倍,对象头部分正好是8字节的倍数,当对象实例数据没有对齐的时候,需要对齐填充来补齐。因此这部分的数据是没有意义的,只是用来填充。
注释:需要对齐填充的原因有蛮多的,包括:CPU的读取效率,因为地址总线的原因,在寻址过程当中不对齐的情况读取一个8比特的数据需要对读取两次,而写的时候效率就更低了,需要写两次,然后组合起来的数据才是我们要写入的数据,这样子写入的效率是很低的。我们定义数据库字段的长度的时候最好也是定义成8的倍数。
三、对象的访问定位:
访问一个对象我们主要需要知道两个信息,一个是这个对象是什么(对象的类型数据),另一个是这个对象有什么(对象的实例数据)。
以前我们都知道,对象是放在堆中的,而栈中存放着指向堆相应位置的内存地址。
应该说栈上存放的是reference数据,用来操作堆上的具体对象。
java虚拟机规范中规定了reference数据是一个指向对象的引用,并没有定义这个引用是怎么去定位堆中的对象的位置和访问堆中的对象的。所以访问堆中的对象的方式也是取决于虚拟机的实现方式:
1、使用句柄:
(1)、使用句柄访问的方式的话堆中会有一个区域用来作为句柄池,reference数据存放的就是对象的句柄地址。
(2)、句柄中包含了对象实例数据与类型数据各自的具体地址信息。
原理图如下:

句柄访问的方式就是虚拟机栈中存放着reference数据,reference数据当中存放着指向对象的实例数据指针和指向对象的类型数据的指针,这些指针指向相应的对象实例数据和对象类型数据。
2、使用直接指针:
使用直接指针的访问方式,java堆中就需要考虑怎么去放置访问类型数据的相关信息,reference数据当中存储的直接就是对象地址。
前文有提到的,对象的内存分配包括三部分内容:对象头、实例数据,对齐填充。对象头当中就存放着指向对象类型数据的指针。

优缺点:使用句柄访问方式的最大好处是未定,因为里面存放的是稳定的句柄地址,对象被移动时只会改变句柄当中的实例数据指针,reference数据本身不需要改变,
使用直接指针访问好处是速度快,因为少掉了一次指针定位的时间开销(句柄访问方式两次指针定位:对象的实例数据指针到对象的实例数据,对象的类型数据指针到对象的类型数据,而直接指针方式的reference数据直接存放着对象实例数据的地址,只要从对象的对象头当中读取对象的类型数据地址定位到对象的类型数据就可以了)。
Java虚拟机-对象的创建和访问的更多相关文章
- 深入理解java虚拟机---对象的创建过程(八)
1.对象的创建过程 由于类的加载是一个很复杂的过程,所以这里暂时略过,后面会详细讲解,默认为是已加载过的类.着重强调对象的创建过程. 注意: 最后一步的init方法是代码块和构造方法. 以上是总图,下 ...
- 深入理解java虚拟机---对象的访问定位(十)
引用其他人的文章: https://www.cnblogs.com/YYfish/p/6722258.html 那是怎么访问对象呢? java 程序是通过栈上的reference数据来操作堆上的具体对 ...
- java面试-对象的创建、内存布局、访问定位
一.对象的创建 1.虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有,那必须先执行相应的 ...
- 深入理解JVM(4)——对象的创建和访问
1.对象的创建 在语言层面上,创建对象(例如克隆,反序列化)通常仅仅是一个new关键字而已. 在虚拟机中,对象(文中讨论的对象限于普通 Java 对象,不包括数组和 Class 对象等)的创建过程如下 ...
- java中对象的创建过程
public class Test1 { public static void main(String[] args) { new B(); System.out.println("---- ...
- 深入理解java虚拟机---对象的结构(九)
注意: 我们可以看到的就是InstanceData的数据. 先转载一篇文章作为开头,因为讲的非常详细,我就简单加工下放到这里: 对象结构 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区 ...
- 【Java】对象的创建过程
一.对象的创建过程 1.首次创建对象时或该类静态方法/静态域首次被访问时,JAVA解释器查找该类的路径,定位该类的class文件 2.载入该类的class文件,有关静态初始化的所有动作执行,但是只执行 ...
- 【003】【Java虚拟机——对象死亡的判断】
对象死亡! 垃圾收集器在对堆进行回收前,首先要做的事情就是要确定这些对象之中哪些还"存活"着, 哪些已经"死去" (即不可能再被不论什么途径使用的对象). 1) 引用计 ...
- HotSpot虚拟机对象的创建过程
1.文中讨论的对象限于普通Java对象,不包括数组和class对象. 2.内存的分配方式由Java堆是否规整来决定,而Java堆是否规整取决于垃圾收集器是否有压缩整理的功能. 3.还需要考虑:对象的创 ...
随机推荐
- Numpy系列(十)- 掩码数组
简介 有时候数据集中存在缺失.异常或者无效的数值,我们可以标记该元素为被屏蔽(无效)状态. import numpy as np import numpy.ma as ma x = np.array( ...
- GDB disassemble
前面几篇谈GDB调试程序的帖子,都对反汇编语焉不详.这里详细讨论一下disassemble/disass命令 反汇编一个函数disass func_name 反汇编一段内存地址, 第1个参数是起始地址 ...
- Object is not a function
如图报了一个这样的错,百度好多都说是函数名和html元素重名的问题.可是这个问题我想我这里是不存在的 可以看到就一个绑定事件,而且id名不是关键字 报错是在$.ajax这一行,索性就把submit-i ...
- MySQL初步
一 写在开头1.1 本节内容本节的主要内容是MySQL的基本操作(来自MySQL 5.7官方文档). 1.2 工具准备一台装好了mysql的ubuntu 16.04 LTS机器. 二 MySQL的连接 ...
- [物理学与PDEs]第5章第3节 守恒定律, 应力张量
5. 3 守恒定律, 应力张量 5. 3. 1 质量守恒定律 $$\bex \cfrac{\p \rho}{\p t}+\Div_y(\rho{\bf v})=0. \eex$$ 5. 3. 2 应 ...
- [物理学与PDEs]第1章习题14 求解 rot 方程
设向量函数 ${\bf B}(x,y,z)=(B_x,B_y,B_z)$ 在 $z\neq 0$ 时具有一阶连续偏导数, 在 $z=0$ 时具有第一类间断, 且 $$\bex \Div{\bf B}= ...
- fstat函数
一.函数原型 #include<sys/stat.h> #include<unistd.h> int fstat(int fildes,struct stat *buf); 返 ...
- Oracle10gXE和Oracle SQL Developer本地安装配置
第1部分 Oracle10gXE安装 Oracle10gXE安装的安装几乎是一路next就可以安装好:但是中间设置的用户名.密码.口令.SID等信息一定记住,后面需要使用. 第2部分 Oracle S ...
- [译]Ocelot - Configuration
原文 这里有一个配置的样例.配置主要有两个部分.一个是ReRoutes数组,另一个是GlobalConfiguration.ReRoute告诉Ocelot怎么处理上游的请求.Global config ...
- Vue2.0的三种常用传值方式、父传子、子传父、非父子组件传值
参考链接:https://blog.csdn.net/lander_xiong/article/details/79018737