各大网站vip接口建设,手机发博客wordpress,企业网站建设的方法,自己做网站开发目录
一、对象的实例化
#xff08;一#xff09;创建对象的方式
#xff08;二#xff09;创建对象的步骤
二、对象的内存布局
#xff08;一#xff09;对象头
#xff08;二#xff09;实例数据
#xff08;三#xff09;对齐填充
三、 对象的访问定位
一创建对象的方式
二创建对象的步骤
二、对象的内存布局
一对象头
二实例数据
三对齐填充
三、 对象的访问定位
一使用句柄
二直接指针
三对比 一、对象的实例化
一创建对象的方式
newClass的newInstanceConstructor的newInstance使用clone使用反序列化第三方库Objenesis
二创建对象的步骤
1、判断对象对应的类是否加载、链接、初始化
当虚拟机遇到一条字节码new指令时。首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用并且检查这个符号引用代表的类是否被加载解析初始化过。如果没有在双亲委派模式下使用当前类加载器以ClassLoader包名类名为key值进行查找对应的.class文件如果没有找到文件则抛出ClassNotFoundException异常
2、为对象分配内存
首先计算对象占用空间的大小接着在堆中划分一块内存给新对象如果实例成员变量是引用变量仅分配引用变量空间即可即4个字节大小
如果Java堆内存中不规则虚拟机就必须维护一个列表记录哪些内存可用哪些不可用。分配的时候在列表中找一个足够大的空间分配然后更新列表。这种分配方式叫空闲列表(Free List)。
选择哪种由Java堆是否规整决定Java堆是否规整由所采用的的垃圾收集器是否带有空间压缩整理Compact的能力决定
当使用SerialParNew等带有压缩整理过程的收集器指针碰撞简单高效当使用CMS基于清除Sweep算法收集器时只能采用空闲列表来分配内存CMS为了能在多数情况下分配内存更快设计了一个Linear Allocatioin Buffer的分配缓冲区通过空闲列表拿到一大块分配缓冲区后在它里面仍可使用指针碰撞方式分配
假设Java 堆中内存时绝对规整的所有被使用过的内存放在一边空闲的内存放在另一边中间放一个指针作为分界点指示器。那么内存分配就是指针指向空闲的方向挪动一段与对象大小相等的举例。这种分配方式成为指针碰撞Bump The Pointer。
3、处理并发安全问题
对象创建是非常频繁的行为还需要考虑并发情况下仅仅修改一个指针所指向的位置也是不安全的例如正在给对象A分配内存指针还未修改对象B又使用原来的指针分配内存。解决问题有两种可选方案
a、对分配内存空间的动作进行同步处理。实际上虚拟机采取CAS配上失败重试的方式保证更新操作的原子性。b、把内存分配的动作按照线程划分到不同的空间中进行每个线程在Java堆中预先分配一小块内存称为本地线程分配缓冲Thread Local Allocation BufferTLAB只有本地缓冲区用完了分配新的缓存区时才需要同步锁定。
虚拟机是否使用TLAB可以通过-XX: /-UseTLAB参数来设定。
4、初始化分配到的空间
内存分配完成后虚拟机将分配到的内存空间不包括对象头都初始化为零值。如果使用了TLAB这个工作可以提前到TLAB分配时进行。 这步操作保证对象的实例字段在Java代码中可以不赋初始值就直接使用程序可以访问到字段对应数据类型所对应的零值。
5、设置对象的对象头
接下来Java虚拟机还要对对象进行必要的设置例如对象时哪个类的实例、如何才能找到类的元数据信息对象的哈希码实际上对象的HashCode会延后真正调用Object::hashCode()方法时才计算、对象的GC分代年龄等信息。这些信息存放到对象的对象头Object Header
6、执行init方法进行初始化
上面工作完成后从虚拟机角度来说一个新的对象已经产生了但是从Java程序的视角来说对象创建才刚刚开始对象的构造方法Class文件中init()方法还未执行所有字段都是默认的零值。new指令之后接着执行init方法按照程序员的意愿对对象进行初始化这样一个真正可用的对象才算完全构造出来
二、对象的内存布局
一对象头
对象头包括两部分第一类是存储对象自身运行时的数据Mark Word第二类是类型指针
这部分数据的长度在32位和64位的虚拟机未开启指针压缩中分别是32bit和64bit官方称为【Mark Word】运行时元数据其包括
哈希值GC分代年龄锁状态标志线程持有的锁偏向线程ID偏向时间戳
对象头里的信息是与对象自身定义的数据无关的额外存储成本考虑到虚拟机的空间效率根据对象状态的不同Markword可以复用自己的空间。 类型指针
即对象指向它的类型元数据的指针Java虚拟机通过这个指针来确认该对象属于哪个类的实例 说明如果对象是一个Java数组那在对象头中还必须有一块用于记录数组长度的数据因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小但是如果数组的长度是不确定的将无法通过元数据中的信息推断出数组的大小。 二实例数据
对象的实例数据部分是对象的真正存储的有效信息即我们在程序代码中定义的各种类型的字段内容无论是父类继承下来还是子类中定义的字段都要记录下来。
1、这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。 2、分配策略参数-XX:FieldsAllocationStyle 3、HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oopsOrdinary Object Pointers 4、从默认的分配策略中可以看出相同宽度的字段总被分配到一起存放。 5、在满足这个前提条件的情况下在父类中定义的变量会出现在子类之前。 6、如果HotSpot虚拟机的XXCompactFields参数值为true默认也是true那么子类中较窄的变量也允许插入父类变量的空隙之间以节省一点点空间。
三对齐填充
对齐填充这并不是必然存在没有特别的意义它仅仅起着占位符的作用。
因为HotSpot虚拟机自动内存管理系统要求对象的起始地址必须是8字节的整数倍换句话就是任何对象的大小都必须是8字节的整数倍。 对象头已经精心设计为8字节的整数倍1倍或者2倍。对象实例数据部分如果没有对齐的话就需要通过对其填充来补全。
三、 对象的访问定位
一使用句柄
使用句柄Java堆中将划出一块内存作为句柄池reference中存储的就是对象的句柄地址句柄包含对象实例数据与类型数据各自的具体信息。
二直接指针
使用指针reference中存储的直接就是对象地址如果访问对象本身不需要多一次的间接访问的开销。 三对比
两种方式各有优势
使用句柄最大好处是reference中存放的是稳定句柄地址在对象被移动垃圾搜集时会产生时只改变句柄中实例数据指针reference本身不用改变。使用指针最大好处就是速度快节省了一次指针定位的时间开销由于对象访问在Java中非常频繁所以积少成多也是一项可观的执行成本。HotSpot主要是用指针进行对象访问例外情况如果使用Shenandoah收集器的话也会有一次额外的转发。