首页 > 系统相关 >JVM(八)对象的实例化内存布局与访问定位

JVM(八)对象的实例化内存布局与访问定位

时间:2023-07-12 10:47:58浏览次数:120  
标签:对象 句柄 引用 访问 实例 内存 JVM 指针

JVM(八)对象的实例化内存布局与访问定位


1 对象创建的方式

  • new

    • 变形1:
  • Class的newInstance(),即反射

    Class的newInstance反射的使用较为苛刻,要求只能调用空参的构造器,而且权限必须是public

    这种方式再jdk9中被标记为过时了

  • Constructor的newInstance(),也属于是反射

    可以调用空参或者无参的构造器,权限没有要求

  • 使用clone()方法:不调用任何构造器,但要求当前类实现Cloneable接口

  • 使用反序列化:从文件或者网络中获取对象的二进制流,然后序列化为java对象

  • 使用第三方库,如Objenesis,可以利用一些字节码技术动态生成对象

2 对象创建的步骤

2.1 从字节码的角度看对象的创建

​ 对下面代码编译后的字节码文件进行反编译:

  • 首先调用new操作符指令创建运行时常量池索引为2的类的对象并进行零值初始化,在此之前也需要检查这个类是否被加载
    • 这一步主要有两个工作:1 将类加载到方法区 2 在堆中开辟创建对象的空间
  • dup指令在栈帧的操作数栈中将指向创建对象的引用复制一份,这样就有两个引用指向堆空间的对象实体,栈底部的引用负责对对象进行赋值操作;栈顶的引用则是作为一个句柄调用相关方法;也可以看到操作数栈的深度为2
  • invokespecial #1指令调用运行时常量池索引为1的方法引用,即是java/lang/Object."<init>":()V,Object的空参构造器和代码块的赋值,对对象实例完成赋值操作
  • astore_1指令操作数栈结果放入局部变量表下标为1的位置(0是String[] args形参)
public class ObjectTest {
    public static void main(String[] args) {
        Object obj = new Object();
    }
}

...
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
            8       1     1   obj   Ljava/lang/Object;
...
2.1 从执行的步骤看对象的创建
  • 判断对象对应的类是否被加载、链接和初始化,虚拟机在遇到一条new指令的时候,首先去检查这个指令的参数能否在MetaSpace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载、链接和初始化(即判断类元信息是否存在)。如果没有则需要在双亲委派模式下,使用对应的类加载器以ClassLoader+包名+类名为key查找对应的class文件,如果没有找到则抛出ClassNotFoundException异常,否则将类元信息加载到方法区,并生成对应的Class类对象

  • 为对象分配内存,首先计算对象占用空间大小(int、引用等占4字节,double、long占8)

    • 如果内存规整,即有一段连续的内存来存储对象,虚拟机采用指针碰撞来为对象分配内存,即将所有用过的内存放在一遍,没有用过的放在另一边,指针作为分界点的指示器,分配内存的时候就把指针向空闲的一侧移动与对象大小相等的距离。一般使用带有Compact(整理)过程的收集器、基于压缩算法的如Serial、ParNew使用指针碰撞的方式为对象分配内存
    • 如果内存不规整,则采用空闲列表法为对象分配内存。即虚拟机维护一个列表,用于记录哪些内存块是可用的,再分配的时候从列表中找一块足够大的空间进行分配并更新列表即可
  • 处理并发安全问题

    • 首先为每个线程都在堆上创建一个TLAB,线程会优先在自己这块内存区域创建对象
    • 如果TLAB不足以分配对象,则采用CAS配上失败重试保证更新的原子性
  • 初始化分配到的空间,为所有的属性设置零值,保证对象实例字段在不被赋值的情况下也能直接使用

  • 设置对象头

  • 执行<init>方法进行初始化

    <init>方法包括成员变量的赋值语句、实例化代码块以及类的构造方法的整合,并把堆内对象的首地址赋值给引用变量

3 对象的内存布局

  • 对象头(Header),包括两部分:
    • 运行时元数据,包括:
      • 哈希值(HashCode):表示对象在内存中的首地址,方便变量进行引用
      • GC分代年龄
      • 锁状态标志
      • 线程持有的锁
      • 偏向线程ID
      • 偏向时间戳
    • 类型指针:指向类元数据Instance Klass,确定对象所属的类型(对象.getClass()就是获取的类型指针)
  • 实例数据(Instance Data):对象真正存储的有效信息,包括程序代码定义和从父类继承来的各种类型的字段;并且具有以下规则:
    • 相同宽度的字段总是被分配在一起(4字节、8字节变量)
    • 分类中定义的变量出现在子类之前
    • 如果CompactFields参数为true(默认true),子类的窄变量可以插入到父类变量的空隙中以节省空间
  • 对齐补充(Padding):保证对象占有空间是8字节的倍数,不是必须也没有特别的含义
public class CustomerTest {
    public static void main(String[] args) {
        Customer cust = new Customer();
    }
}

class Customer {
    Integer id;
    String name;
    Account acct;
}

class Account {
    ...
}
image-20230531161926536

4 对象访问定位

JVM是如何通过栈帧中的对象引用访问到其内部的对象实例的呢?
image-20230531163542212
句柄访问
image-20230531163704455
  • 句柄访问是指在java堆中开辟一块句柄池,包括堆中到对象实例数据的指针和到方法区中到对象类型数据的指针
  • 每个对象会对应一个句柄池
直接指针访问
image-20230531163945216
  • 直接指针访问是指栈帧局部变量表中的对象引用直接指向对象实例数据,然后在对象实体中有一个指向方法区中对象类型数据的指针
两种方式的优缺点?
  • 句柄访问的方式需要在堆空间中开辟额外空间,并且在进行对象访问定位的时候需要先定位到句柄才能定位到对象实体,效率较低
  • 如果发生对象的移动,即对象的存储地址发生变化,则直接指针的访问方式需要去修改每个指向对象实体的对象引用,而句柄访问的方式只需要改一次句柄的指针即可

标签:对象,句柄,引用,访问,实例,内存,JVM,指针
From: https://www.cnblogs.com/tod4/p/17546893.html

相关文章

  • JVM(七)方法区
    JVM(七)方法区1方法区方法区和Java堆一样,是各个线程共享的内存区域,用于存储编译后的字节码中的类的机构信息,如运行时常量池、属性方法数据以及方法、构造器的字节码方法区在JVM启动的时候被创建,并且它的实际物理内存空间和Java堆区一样都是可以不连续的;大小可以选择固定大小或......
  • JVM(十)StringTable
    JVM(十)StringTable1String的基本特性String即字符串,通过一对引号""表示,String创建的方式主要有Strings="abc";//字面量的方式Strings=newString("abc");//类创建new方式String声明为final,不可以被继承String实现了Serializable接口,表示字符串是支持序列......
  • JVM(九)执行引擎
    JVM(九)执行引擎1执行引擎概述执行引擎是Java虚拟机核心的组成部分之一虚拟机是一个相对于物理机的概念,这两种机器都有代码执行能力,区别在于物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎是由软件自主实现的,因此可以不受物理条件......
  • JVM(十三)分代收集、增量收集以及分区算法
    JVM(十三)分代收集、增量收集以及分区算法1分代收集算法​ 前面的所有算法中,没有一种算法能够完全替代其他算法,它们都有自己独特的优势和特点,分代收集算法应运而生:分代收集算法对不同生命周期的对象采取不同的收集方式,一般划分为新生代和老年代,以便提高回收效率在Java程序......
  • JVM(十二)垃圾清除阶段算法
    JVM(十二)垃圾清除阶段算法垃圾清除阶段是指,当成功区分出内存区域中的存活对象和死亡对象之后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。目前在JVM中比较常见的三种垃圾收集算法是标记-清除算法(Mark-Sweep)......
  • JVM(十一)垃圾回收概述和垃圾标记阶段的算法
    JVM(十一)垃圾回收概述和垃圾标记阶段的算法1Java垃圾回收概述什么是垃圾?垃圾是在程序运行过程中不被任何指针指向的对象,这个对象就是需要被回收的垃圾为什么要进行垃圾回收?如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占内存空间会一直保存到应用程序结束,被......
  • 一个高性能、低内存文件上传流.Net组件
    推荐一个用于轻松实现文件上传功能的组件。项目简介一个基于.NET平台的开源项目,提供了一个简单易用的API,可以在Web应用程序中快速集成文件上传功能。优化多部分流式文件上传性能:减少25%的CPU使用量、50%内存。项目特点1、简单易用的API: 提供了简单的API,可以轻松地集成......
  • EDGE 浏览器占用内存优化
    windows+s搜索service打开服务;找到下面edge三项双击把启动类型都改成手动触发  ......
  • 初识虚拟机JVM
    初识JVM(JAVAVirtualMachine)​ JVM是一种规范,可以使用软件来实现,也可以使用硬件来实现,就是一个虚拟的用于执行bytecodes字节码的计算机。他也定义了指令集、寄存器集、结构栈、垃圾收集堆、内存区域。​ JVM负责将java字节码解释运行,边解释边运行,这样,速度就会受到一定的影......
  • vue学习——vue实例与组件
     vc不是通用的称呼,官方的说法是组件实例对象 组件实例的原型对象的原型对象是vue的原型对象 ......