Java对象--实例化/内存布局/访问定位

Java对象--实例化/内存布局/访问定位_第1张图片

对象的实例化

创建对象方式

  • 通过new方式创建(包括工厂方法获取或者单例方式获取)
  • 通过反射方式(如:Class.newInstance())
  • 通过clone()方法(当前类实现cloneable接口-浅复制)
  • 使用反序列化(从文件或者网络中获取对象的二进制流进而获取对象)
  • 通过第三方库(objenesis)

创建对象步骤

Java对象--实例化/内存布局/访问定位_第2张图片 对象实例化主要过程

  • 判断对象对应的类是否经过加载、链接、初始化:虚拟机识别到new指令时,首先检查该类是否在方法区常量池中存在符号引用,并检查该引用是否已经被加载连接初始化[即方法区是否存在该类元信息];如果不存在,则通过双亲委派机制寻找class文件,如果没找到,则报错classNotfoundException,如果找到,则进行类加载并生成对应的类信息
  • 为对象分配内存
    • 栈中申请内存存储对象引用地址
    • 计算对象占用空间大小,在堆中划分区域给对象(int/byte/short/string[引用类型]/float等是4个字节,double/long则是8个字节)
    • 处理并发安全问题:由于堆是共享的所以存在多线程安全问题;通常采用CAS失败重试、区域加锁保证原子性,或者通过给每个线程预先分配一块TLAB
  • 对象默认初始化:对所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用
  • 设置对象头:将对象所属类的[即类的元数据信息]、对象的hashcode、对象的GC分代age、锁信息等数据存储在对象的对象头中
  • 执行构造函数Init方法进行初始化:初始化成员变量、执行实例化代码块、调用类的构造方法、并把堆内对象的地址赋值给栈中的引用变量等

对象的内存布局

Java对象--实例化/内存布局/访问定位_第3张图片

对象在堆内存中存储内容包括对象头、实例数据、对齐填充

对象头(Header)

对象头由两部分组成,运行时元数据和类型指针

  • 运行时元数据(mark word)
    • HashCode值(一般为栈的引用地址)
    • GC分代年龄
    • 锁状态标志
    • 线程持有的锁
    • 偏向线程ID、偏向时间锁
  • 类型指针
    指向类元数据的Class,确定对象所属的类型

实例数据(Instance Data)

对象真正存储的有效信息,包括各种类型字段(包括父类字段信息等)

对齐填充(padding)

非必须的,没有特别含义,主要起到占位符的作用

对象的访问定位

JVM通过栈帧的对象引用地址访问到堆内存对象实例数据(对象中各个实例字段的数据),然后再通过类型数据指针访问方法区中对象类型数据(对象的类型、父类、实现的接口、方法、静态变量,静态块等),有两种方式:句柄访问和直接指针

句柄访问

使用句柄访问,在java堆中将划分出一块内存来作为句柄池。reference中存储的就是对象的句柄地址,而句柄中包含对象实例数据与类型数据具体地址信息

Java对象--实例化/内存布局/访问定位_第4张图片

 直接访问(Hotspot默认方式)

使用直接访问,在java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息。而reference中直接存储的是对象引用地址

Java对象--实例化/内存布局/访问定位_第5张图片

总结:直接访问比句柄访问节省空间,速度快;

你可能感兴趣的:(后端,java)