网站的icp备案信息是什么,百度竞价推广代运营公司,诺邯郸网站建设,课件模板文章目录 1、对象的实例化1.1、创建对象的方式1.2、创建对象的步骤 2、对象的内存布局3、对象的访问定位3.1、对象访问的定位方式3.2、使用句柄访问3.3、使用指针访问 4、小结 平时大家经常使用new关键字来创建对象#xff0c;那么我们创建对象的时候#xff0c;怎么去和运行… 文章目录 1、对象的实例化1.1、创建对象的方式1.2、创建对象的步骤 2、对象的内存布局3、对象的访问定位3.1、对象访问的定位方式3.2、使用句柄访问3.3、使用指针访问 4、小结 平时大家经常使用new关键字来创建对象那么我们创建对象的时候怎么去和运行时数据区关联起来呢本贴将会带着这样的问题来重点讲解对象实例化的过程和方式包括对象在内存中是怎样布局的以及对象的访问定位方式带领大家更加深入地学习对象的实例化布局。 1、对象的实例化
对象的实例化将分成两部分讲解第一部分为创建对象的方式第二部分为创建对象的步骤。如下图所示是对象实例化的整体结构图
1.1、创建对象的方式
创建对象的方式有多种例如使用new关键字、Class的newInstance()方法、Constructor类的newInstance()方法、clone()方法、反序列化、第三方库Objenesis等如下图所示 每种创建对象方式的实际操作如下
使用new关键字——调用无参或有参构造器创建。使用Class的newInstance()方法——调用无参构造器创建且需要是public的构造器。使用Constructor类的newInstance()方法——调用无参或有参、不同权限修饰构造器创建实用性更广。使用clone()方法——不调用任何构造器且对象需要实现Cloneable接口并实现其定义的clone()方法且默认为浅复制。使用反序列化——从指定的文件或网络中获取二进制流反序列化为内存中的对象。第三方库Objenesis——利用了asm字节码技术动态生成Constructor对象。
Java是面向对象的静态强类型语言声明并创建对象的代码很常见根据某个类声明一个引用变量指向被创建的对象并使用此引用变量操作该对象。在实例化对象的过程中JVM中发生了什么变化呢
下面从最简单的Object ref new Object()代码进行分析利用javap -verbose -p命令查看对象创建的字节码如下图所示 各个指令的含义如下
new首先检查该类是否被加载。如果没有加载则进行类的加载过程如果已经加载则在堆中分配内存。对象所需的内存的大小在类加载完成后便可以完全确定为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。这个指令完毕后将指向实例对象的引用变量压入虚拟机栈栈顶。dup在栈顶复制该引用变量这时的栈顶有两个指向堆内实例对象的引用变量。invokespecial调用对象实例方法通过栈顶的引用变量调用方法。是对象初始化时执行的方法而是类初始化时执行的方法。
从上面的四个步骤中可以看出需要从栈顶弹出两个实例对象的引用。这就是为什么会在new指令下面有一个dup指令。其实对于每一个new指令来说一般编译器都会在其下面生成一个dup指令这是因为实例的初始化方法方法肯定需要用到一次然后第二个留给业务程序使用例如给变量赋值、抛出异常等。如果我们不用那编译器也会生成dup指令在初始化方法调用完成后再从栈顶pop出来。
1.2、创建对象的步骤
前面所述是从字节码角度看待对象的创建过程现在从执行步骤的角度来分析如下图所示
创建对象的步骤如下
1、判断对象对应的类是否加载、链接、初始化虚拟机遇到一条new指令首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用并且检查这个符号引用代表的类是否已经被加载、解析和初始化即判断类元信息是否存在。如果没有那么在双亲委派模式下使用当前类加载器以“ClassLoader包名类名”为Key查找对应的“.class”文件。如果没有找到文件则抛出ClassNotFoundException异常。如果找到则进行类加载并生成对应的Class类对象。2、为对象分配内存首先计算对象占用空间大小接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量仅分配引用变量空间即可即4字节大小。如果内存规整使用指针碰撞。如果内存是规整的那么虚拟机将采用指针碰撞法(Bump The Pointer)来为对象分配内存。意思是所有用过的内存在一边空闲的内存在另外一边中间放着一个指针作为分界点的指示器分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离罢了。一般使用带有compact整理过程的收集器时使用指针碰撞例如Serial Old、Parallel Old等垃圾收集器。如果内存不规整虚拟机需要维护一个列表使用空闲列表(Free List)分配。如果内存不是规整的已使用的内存和未使用的内存相互交错那么虚拟机将采用空闲列表法来为对象分配内存。意思是虚拟机维护了一个列表记录哪些内存块是可用的在分配的时候从列表中找到一块足够大的空间划分给对象实例并更新列表上的内容。这种分配方式称为空闲列表。选择哪种分配方式由Java堆是否规整决定而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。3、处理并发安全问题创建对象是非常频繁的操作在分配内存空间时另外一个问题是保证new对象的线程安全性。虚拟机采用了两种方式解决并发问题。CAS(Compare And Swap)是一种用于在多线程环境下实现同步功能的机制。CAS操作包含三个操作数内存位置、预期数值和新值。CAS的实现逻辑是将内存位置处的数值与预期数值相比较若相等则将内存位置处的值替换为新值若不相等则不做任何操作。TLAB把内存分配的动作按照线程划分在不同的空间之中进行即每个线程在Java堆中预先分配一小块内存。4、初始化分配到的空间内存分配结束虚拟机将分配到的内存空间都初始化为零值不包括对象头。这一步保证了对象的实例字段在Java代码中可以不用赋初始值就可以直接使用程序能访问到这些字段的数据类型所对应的零值。5、设置对象的对象头将对象的所属类即类的元数据信息、对象的HashCode、对象的GC信息、锁信息等数据存储在对象头中。这个过程的具体设置方式取决于JVM实现。6、执行init()方法进行初始化从Java程序的视角看来初始化才正式开始。初始化成员变量执行实例化代码块调用类的构造方法并把堆内对象的首地址赋值给引用变量。因此一般来说由字节码中是否跟随由invokespecial指令所决定new指令之后接着就是执行方法把对象按照程序员的意愿进行初始化这样一个真正可用的对象才算完全创建出来。
2、对象的内存布局
对象的内存布局如下图所示 在HotSpot虚拟机中对象在内存中的布局可以分成对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)三部分。
对象头主要包括对象自身的运行时元数据比如哈希值、GC分代年龄、锁状态标志等同时还包含一个类型指针指向类元数据表明该对象所属的类型。此外如果对象是一个数组对象头中还必须有一块用于记录数组的长度的数据。因为正常通过对象元数据就知道对象的确切大小。所以数组必须得知道长度。实例数据它是对象真正存储的有效信息包括程序代码中定义的各种类型的字段包括从父类继承下来的和本身拥有的字段。对齐填充由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数1倍或者2倍因此如果对象实例数据部分没有对齐的话就需要通过对齐填充来补全。它不是必要存在的仅仅起着占位符的作用。
对象的内存布局示例如下图所示 下面我们用代码来讲述实例在内存中的布局如下代码清单所示 把CustomerTest中main()方法看作是主线程主线程虚拟机栈中放了main()方法的栈帧其中栈帧里包含了局部变量表、操作数栈、动态链接、方法返回地址、附加信息等结构。局部变量表对于main()方法来讲第一个位置放的是args第二个位置放的是cust,cust指向堆空间中new Customer()实体。Customer对象实体整体来看分为对象头、实例数据、对齐填充。对象头中主要有运行时元数据和元数据指针元数据指针也可称为类型指针运行时元数据包含哈希值、GC分代年龄、锁状态标志等信息类型指针指向当前对象所属类的信息也就是方法区的Customer类的Klass类元信息Klass类元信息包括对象的类型信息在实例数据中包含父类的实例数据对于当前对象来讲它有id、name、acct三个变量name的字符串常量放在堆空间的字符串常量池中成员变量acct指向new Account()对象实例在堆中的内存地址new Account()对象实例的对象头中也维护了一个类型指针指向方法区的Account的Klass类元信息。整体布局如下图所示
3、对象的访问定位
3.1、对象访问的定位方式
前面讲解了创建对象的方式以及对象的内存结构。创建好对象之后接下来就是去访问对象那么JVM是如何通过栈帧中的对象引用访问到其内部对象实例的呢如下图所示 通常来讲栈帧存储指向堆区中的对象地址对象中含有该类对象的类型指针也就是我们说的元数据指针如果访问对象只需要访问栈帧中的地址即可。
《Java虚拟机规范》没有对访问对象做具体的说明和要求所以对象访问方式由虚拟机实现而定。主流有两种方式分别是使用句柄访问和使用直接指针访问。
3.2、使用句柄访问
堆需要划分出一块内存来做句柄池reference中存储对象的句柄池地址句柄中包含对象实例与类型数据各自具体的地址信息如下图所示 这样做的好处是reference中存储稳定句柄地址对象被移动垃圾收集时移动对象很普遍时只会改变句柄中实例数据指针reference本身不需要被修改。但是这样做会造成多开辟一块空间来存储句柄地址相当于是间接访问对象。
3.3、使用指针访问
reference中存储的就是对象的地址如果只是访问对象本身的话就不需要多一次间接访问的开销。
这样做的好处是访问速度更快Java中对象访问频繁每次访问都节省了一次指针定位的时间开销。HotSpot虚拟机主要使用直接指针访问的方式如下图所示 JVM可以通过对象引用准确定位到Java堆区中的对象这样便可成功访问到对象的实例数据。JVM通过存储在对象中的元数据指针定位到存储在方法区中的对象的类型信息即可访问目标对象的具体类型。
4、小结
讲解了多种创建对象的方式如使用new关键字、Class的newInstance()方法、Constructor类的newInstance()方法等。紧接着讲解了创建对象的步骤总共分为6步第1步是判断对象对应的类是否加载、链接、初始化第2步是为对象分配内存第3步是处理并发安全问题第4步是初始化分配到的空间第5步是设置对象的对象头第6步是执行init方法进行初始化。接下来讲解了对象的内存布局并且使用案例讲解了对象在内存布局中的内容。最后讲解了访问对象的两种主流方式分别是使用句柄访问和使用指针访问其中经常使用的HotSpot虚拟机主要使用指针访问。