当前位置: 首页 > 科技观察

当一个对象是新的时会发生什么?

时间:2023-03-15 08:56:51 科技观察

1.简介众所周知,Java是一种面向对象的编程语言。我们平时写代码的时候,都是在不断地操作各种对象,那么当你写了这样一行代码,比如Useruser=newUser();,JVM是干什么的呢?二、认识对象1、内存布局Hotspot虚拟机中一个对象的内存布局分为三部分:对象头、实例数据、对齐填充。对象头有两部分信息。第一部分用于存放对象本身的运行数据(HashCode、GC分代年龄、锁状态标志等)。另一部分是类型指针,它指向它的类元数据,虚拟机通过这个指针来判断这个对象是哪个类实例(如果使用句柄池方法就没有)。如果是数组,就会有数组长度的记录如下表:MarkWord是一种非固定的数据结构,在很小的空间里存储尽可能多的信息,它会复用根据对象的状态有自己的存储空间。各状态下的存储内容如下表所示:实例数据部分是实际存储的有效信息,即代码中定义的各类字段的内容。无论是从父类继承还是在子类中。Alignmentpadding不一定存在,它只是起到占位符的作用,因为HotSpot虚拟机要求对象的起始地址必须是8字节的整数倍。2.对象访问在Java程序中,我们通过对这个对象的引用来操作一个对象。我们都知道对象存在于堆中,这个引用存在于虚拟机栈中。那么引用是如何使用来定位对象在堆中的位置的呢?直接指针法(HotSpot实现):引用中直接存放的是对象在堆中的地址。优点是定位速度快,缺点是需要修改对象移动(GC时的对象移动)引用本身。句柄方法:将Java堆的一部分划分为句柄池。引用存储对象的句柄地址,句柄中包含对象实例和类型的具体位置信息。优点是对象移动只改变句柄中的实例数据指针,缺点是定位了两次。3.创建对象的过程上面介绍了对象的基本信息。下面说说创建对象的过程:当虚拟机遇到一条新的指令时,会检查这条指令的参数是否能在常量池中定位到一个类。符号引用,并检查表示的类是否已被类加载器加载。如果未加载,则必须首先执行此类的加载。类加载检查通过后,虚拟机会为新对象分配内存,类加载完成后就可以确定对象所需内存的大小。内存分配完成后,虚拟机需要将对象初始化为零值,以保证可以直接使用对象的实例变量,而无需在代码中赋初值。在类加载的准备阶段,类变量被初始化为零值。在对象头设置必要的信息,比如如何查找类的元数据信息、对象的HashCode、GC生成年龄等。经过以上操作,已经生成了一个新的对象,但是方法有未被执行,所有字段均为零。这时候就需要按照程序员的意愿执行初始化对象的方法(构造函数)。类变量的初始化操作是在类加载的初始化阶段完成的。有两种分配内存的方法:Java堆内存是常规的(使用带压缩的标记或垃圾收集器),以及使用指针指向空闲位置。指针移动的距离等于分配的大小。内存不规则(使用标记和清除垃圾收集器)。虚拟机维护一个可用内存块列表。分配内存时,它从列表中找到足够大的内存空间来划分对象并更新可用内存列表。当找不到足够的内存时,会触发GC分配内存。并发问题解决方案:同步分配内存空间的动作---使用CAS失败重试来保证更新操作的原子性。每个线程在堆中预先分配一小块内存,称为本地线程分配缓冲区(ThreadLocalAllocationBuffer,TLAB),该线程在自己的TLAB上分配内存,只有当TLAB用完后才分配新的TLAB需要同步锁。由-XX:+/-UseTLAB参数设置。4、对象创建指令的重新排序Aa=newA();new一个对象的简单分解:分配对象的内存空间,初始化对象,将引用设置为分配的内存地址,在步骤2之间会发生指令的重新排序3、导致在多线程的时候,如果在初始化之前访问对象,就会出现问题。单例模式的双检测锁模式会有这个问题。您可以使用volatile禁用指令重新排序来解决问题