对象的实例化、内存布局以及访问定位

对象的实例化

创建对象的方式

  • new
    • 包括单例、xxxBuilder、xxxFactory等变形方式
  • 反射
    • Class对象的newInstance()方法,JDK9开始标识为废弃(只能使用无参构造方法,访问权限为public)
    • Constructor的newInstance()方法(可以使用带参构造方法,访问权限没有限制)
  • clone:不使用构造方法,需要类实现Cloneable接口的clone()方法
  • 反序列化:从文件或者网络中获取一个对象的二进制流

创建对象的步骤

  • 判断对象对应的类是否加载、链接、初始化
    • JVM执行对象创建的时候(new对象),首先检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、链接、初始化,即判断类信息是否在方法区中存在。如果没有被加载,则要在双亲委派机制下,使用当前类加载器以Classloader+包名+类名为key来查找class文件,如果没有找到相应的class文件则抛出ClassNotFoundException,如果找到就进行类加载,并生成相应的Class类对象。
  • 为对象分配内存首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象。如果实例中的成员变量是引用变量,仅分配引用变量空间即可,4个字节大小
    • 分配空间的时候判断内存是否规整(内存连续程度)
      • 如果是规整的内存,JVM将采用指针碰撞法(Bump The Pointer)来给对象分配内存
        • 所有用过的内存在一边,空闲的空间在另一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离(一般使用带有compact(整理)过程的收集器时,使用指针碰撞法,如垃圾收集器选择的是Serial、ParNew这种基于压缩算法,JVM采用该分配方式)。
      • 如果内存不规整,JVM需要维护一个列表
        • 空闲列表分配法(Free list):内存不规整,已使用的内存和未使用的内存相互交错,JVM维护一个列表记录哪些内存块是可用的,再分配的时候从列表中找出一块足够的大的空间划分给对象,并更新列表记录内容。
    • 具体使用以上哪种分配算法,取决于Java堆中的内存规整程度,是否碎片化。内存的规整程度又取决于JVM垃圾收集器是否有压缩整理功能来决定
  • 处理并发安全问题
    • 由于堆空间是共享的,多线程情况下会存在都去创建对象的安全问题。
      • 采用CAS失败重试、区域加锁保证更新的原子性
      • 每个线程预先分配一个TLAB区(JDK8默认开启)
  • 初始化分配到的空间
    • 所有属性设置默认值,保证对象实例字段在不赋值的时候可以使用(如int默认值为0,String等引用对象默认为null)
  • 设置对象头
    • 将对象的所属类信息、对象的HashCode和对象的GC信息、锁信息等数据储存在对象的对象头中。这个过程的具体设置细节取决于JVM的具体实现
  • 执行()方法进行初始化(显式的初始化)
    • 实际从Java编码角度来讲,这才是显而易见的初始化:初始化成员变量、执行实例化代码块、调用类构造方法,并把堆内对象的首地址赋值给引用变量

对象的内存布局

对象头Header

  • 运行时元数据(Mask word)
    • 哈希值hashcode
    • GC分代年龄(对象的年龄计数器)、
    • 锁状态的标识
    • 线程池有的锁
    • 偏向线程id
    • 偏向时间戳
  • 类型指针
    • 指向类元数据,指明该对象的类型
  • 如果创建的是数组还需要记录数据的长度

实例数据Instance data

它是对象真正储存的字段有效信息,包括程序代码中定义的各种类型的字段(涵盖父类继承下来的和该类对象本身的)

规则:

  • 相同宽度的字段总是被分配在一起
  • 父类定义的变量会出现在子类之前(现从父类开始加载嘛)
  • 如果CompactFields参数为true(默认为true),子类的窄变量可以放在父类变量的空隙

对齐填充Padding

没有实际含义,起到占位符的作用,可以增加CPU读取的速率?

对象访问定位

句柄访问

  • 局部变量表中的引用对象指向了JVM堆中的句柄
    • 句柄包括两个部分
      • 到对象实例数据的指针:指向JVM的对象实例数据
      • 到对象类型数据的指针:指向方法区的类信息

直接访问

局部变量表中的引用对象直接指向了JVM堆中的对象实例,该对象实例又保存了指向方法区的类型指针。Hotspot VM采用的是直接访问


优缺点:

  • 句柄访问会多出一块内存记录句柄信息,访问不如直接访问
  • 句柄访问情况下,对象的移动只需要改变聚丙种指向对象实例数据的指针即可,无需改变局部变量表中的引用。对象移动发生在例如标记整理算法的时候,会发生对象内存地址的变化,对象的移动。对象在Survivor1和Survivor0中也是会发生互相移动的,在新生代的Eden区到Survivor区,或者晋升到老年代也是同理发生了对象的移动

 

 

 

你可能感兴趣的:(#,JVM,#,Java中常用的必要知识点,JVM,创建对象,对象结构)