JVM深入学习(九)-运行时数据区之对象的布局和定位

6.1 对象创建的方式

  1. new
    1. 单例也算new的方式
    2. 建造者模式和工厂模式产生的对象都是new
      1. StringBuilder
      2. BeanFactory
  1. Class.newInstance
  2. Constructor.newInstance
  3. clone() 需实现clonable接口
  4. 反序列化,可以从二进制流中反序列化出对象
  5. 第三方库Objenesis

测试代码:

package com.zy.study11;


import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import com.zy.study10.MethodAreaTest;

import org.objenesis.ObjenesisStd;


import java.io.*;

import java.lang.reflect.Constructor;

import java.lang.reflect.InvocationTargetException;


/**

 * @Author: Zy

 * @Date: 2021/9/1 14:55

 * 测试对象创建的几种方法

 * 1.new

 * 1.1单例也算new的方式

 * 1.2建造者模式和工厂模式产生的对象都是new

 * 1.2.1StringBuilder

 * 1.2.2BeanFactory

 * 2.Class.newInstance

 * 3.Constructor.newInstance

 * 4.clone() 需实现clonable接口

 * 5.反序列化,可以从二进制流中反序列化出对象

 * 6.第三方库Objenesis 引包

 */

public class ObjectCreateTest {


    static class ObjectCreate implements Cloneable, Serializable {

        private int x;

        private int y;


        public ObjectCreate(int x, int y) {

            this.x = x;

            this.y = y;

        }


        public ObjectCreate() {

        }


        @Override

        protected Object clone() throws CloneNotSupportedException {

            return super.clone();

        }

    }


    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, CloneNotSupportedException, IOException, ClassNotFoundException {

        //1. new

        ObjectCreate objectCreate = new ObjectCreate();

        System.out.println("new对象的内存地址: "+objectCreate);


        //2. Class.newInstance

        ObjectCreate objectCreate1 = ObjectCreate.class.newInstance();

        System.out.println("Class.newInstance对象的内存地址: "+objectCreate1);


        //3.Constructor.newInstance

        ObjectCreate objectCreate2 = ObjectCreate.class.getConstructor().newInstance();

        System.out.println("Constructor.newInstance对象的内存地址: "+objectCreate2);


        // 4.clone

        ObjectCreate clone = (ObjectCreate) objectCreate.clone();

        System.out.println("clone对象的内存地址: "+clone);


        // 5.反序列化

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        new ObjectOutputStream(byteArrayOutputStream).writeObject(objectCreate);

        ObjectCreate unSerializable = (ObjectCreate) new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())).readObject();

        System.out.println("反序列化对象的内存地址: "+unSerializable);


        // 6.第三方库 Objenesis

        ObjenesisStd objenesisStd = new ObjenesisStd();

        ObjectCreate objectCreate3 = objenesisStd.getInstantiatorOf(ObjectCreate.class).newInstance();

        System.out.println("Objenesis生成的内存地址: "+objectCreate3);


    }

}


运行结果:

JVM深入学习(九)-运行时数据区之对象的布局和定位


6.2 对象创建的过程

6.2.1 从字节码中看

一行简单的代码

Object obj = new Object();

查看字节码

Classfile /E:/��Ң/idea��Ŀ/jvm/target/classes/com/zy/study11/ObjectCreateProcessTest.class

  Last modified 2021-9-1; size 491 bytes

  MD5 checksum 26134ce9ee2f969bfbee2050f921671d

  Compiled from "ObjectCreateProcessTest.java"

public class com.zy.study11.ObjectCreateProcessTest

  minor version: 0

  major version: 52

  flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

   #1 = Methodref          #2.#19         // java/lang/Object."<init>":()V

   #2 = Class              #20            // java/lang/Object

   #3 = Class              #21            // com/zy/study11/ObjectCreateProcessTest

   #4 = Utf8               <init>

   #5 = Utf8               ()V

   #6 = Utf8               Code

   #7 = Utf8               LineNumberTable

   #8 = Utf8               LocalVariableTable

   #9 = Utf8               this

  #10 = Utf8               Lcom/zy/study11/ObjectCreateProcessTest;

  #11 = Utf8               main

  #12 = Utf8               ([Ljava/lang/String;)V

  #13 = Utf8               args

  #14 = Utf8               [Ljava/lang/String;

  #15 = Utf8               obj

  #16 = Utf8               Ljava/lang/Object;

  #17 = Utf8               SourceFile

  #18 = Utf8               ObjectCreateProcessTest.java

  #19 = NameAndType        #4:#5          // "<init>":()V

  #20 = Utf8               java/lang/Object

  #21 = Utf8               com/zy/study11/ObjectCreateProcessTest

{

  public com.zy.study11.ObjectCreateProcessTest();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=1, locals=1, args_size=1

         0: aload_0

         1: invokespecial #1                  // Method java/lang/Object."<init>":()V

         4: return

      LineNumberTable:

        line 8: 0

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

            0       5     0  this   Lcom/zy/study11/ObjectCreateProcessTest;


  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 11: 0

        line 12: 8

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

            0       9     0  args   [Ljava/lang/String;

            8       1     1   obj   Ljava/lang/Object;

}

SourceFile: "ObjectCreateProcessTest.java"


可以看到执行的过程:

  1. new命令,用到了#2也就是object的class对象,主要作用是:
    1. 判断这个类是否已经加载到方法区了,如果没有,类加载器进行加载
    2. 在堆中为这个对象开辟内存空间
  1. dup 在栈中创建两个引用指向堆中的内存地址
  2. invokespecial 调用Object类的构造器,#1也可以从常量池中看到就是Object的构造器方法

6.2.2 对象的创建步骤

  1. 对象类型信息的加载,链接,初始化
    1. 查看元空间是否存在该类的类元信息,如果没有,使用类加载器对此类进行加载(双亲委派机制),并生成Class对象,如果加载过程中找不到对应的.class文件,就会报ClassNotFoundException.
  1. 为对象分配内存空间,根据内存空间是否规整来判断,而内存空间是否规整又跟垃圾回收器有关系,有些垃圾回收器有压缩算法,在回收后就导致内存空间不规整.
    1. 内存空间规整
      1. 指针碰撞法分配内存, 内存规整的空间,将已使用内存和未使用内存分开,用指针标记,指针碰撞法就是在分配内存空间之后将指针移动到新分配的内存空间处
    1. 内存空间不规整
      1. 虚拟机维护一个空闲列表,通过空闲列表寻找可以放下此对象的空间
  1. 处理堆内存并发问题
    1. TLAB
    2. CAS失败重试,加锁保证原子性
  1. 对象属性赋默认值
  2. 设置对象头信息
  3. 调用对象构造器,进行对象的显式初始化.

6.2.3 对象的内存布局

  1. 对象头
    1. 运行时元数据
      1. 哈希值
      2. GC分代年龄
      3. 锁状态标志
      4. 线程持有的锁
      5. 偏向线程ID
      6. 偏向时间戳
    1. 类型信息 指向该对象对应的Class对象,其实就是指向方法区中存储的类型信息
    2. 如果是数组对象,还要记录一下数组的长度
  1. 实例数据,存储对象的实际信息
    1. 存储对象的各个字段,包括父类的字段,并且父类的字段在前
    2. 如果CompareFields为true(默认true),子类的窄变量可能存储到父类变量的缝隙
    3. 相同宽度的字段总是被分配到一起
  1. 对齐填充,无实际意义

图解:

JVM深入学习(九)-运行时数据区之对象的布局和定位


6.3 对象的定位

对象创建出来就是用来访问的

对象访问:

  1. 句柄访问
    1. 堆中维护了句柄池,句柄池中有对象实例数据地址和对象类型数据地址,栈中的引用通过句柄池中的可以访问对象实例数据和对象类型数据
    2. 好处: 当堆中的对象实例地址发生了变化时,不必修改栈中引用的地址,修改句柄池中的对象实例地址即可
    3. 坏处: 增加了访问环节
  1. 直接访问 (HotSpot使用)
    1. 如上图内存布局所示,通过栈帧中的引用直接访问对象实例地址,然后再通过对象中的类型指针访问方法区中的对象类型信息
    2. 好处: 直接访问,减少了中间环节,并且堆中不需要额外开辟句柄池
    3. 坏处: 当堆中的对象实例地址发生了变化时,栈帧中局部变量表的变量引用地址也要随之变化
上一篇:JVM 运行时数据区详解,写得非常好!


下一篇:通过分区(Partition)提升MySQL性能