美文网首页
Java虚拟机-如何创建对象

Java虚拟机-如何创建对象

作者: 贪睡的企鹅 | 来源:发表于2019-08-03 17:09 被阅读0次

1 创建对象的方式

1.1 使用new关键字创建对象
Student student = new Student();
1.2 使用Class类的newInstance方法(反射机制)

newInstance方法只能调用无参的构造器创建对象。

Student student2 = (Student)Class.forName("Student类全限定名").newInstance(); 
// 或者
Student stu = Student.class.newInstance();
1.3 使用Constructor类的newInstance方法(反射机制)

java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象,该方法和Class类中的newInstance方法很像,但是相比之下,Constructor类的newInstance方法更加强大些,我们可以通过这个newInstance方法调用有参数的和私有的构造函数。

public class Student {
    private int id;

    public Student(Integer id) {
        this.id = id;
    }

    public static void main(String[] args) throws Exception {

        // 首先得到要实例化类的构造器(有参)
        Constructor<Student> constructor = Student.class
                .getConstructor(Integer.class);
        Student stu3 = constructor.newInstance(123);
    }
}
1.4 使用Clone方法创建对象

无论何时我们调用一个对象的clone方法,JVM都会帮我们创建一个新的、一样的对象,特别需要说明的是,用clone方法创建对象的过程中并不会调用任何构造函数。

public class Student implements Cloneable{
    private int id;

    public Student(Integer id) {
        this.id = id;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }

    public static void main(String[] args) throws Exception {

        Constructor<Student> constructor = Student.class
                .getConstructor(Integer.class);
        Student stu3 = constructor.newInstance(123);
        Student stu4 = (Student) stu3.clone();
    }
}
1.5 使用(反)序列化机制创建对象

当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。

public class Student implements Cloneable, Serializable {
    private int id;

    public Student(Integer id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }

    public static void main(String[] args) throws Exception {

        Constructor<Student> constructor = Student.class
                .getConstructor(Integer.class);
        Student stu3 = constructor.newInstance(123);

        // 写对象
        ObjectOutputStream output = new ObjectOutputStream(
                new FileOutputStream("student.bin"));
        output.writeObject(stu3);
        output.close();

        // 读对象
        ObjectInputStream input = new ObjectInputStream(new FileInputStream(
                "student.bin"));
        Student stu5 = (Student) input.readObject();
        System.out.println(stu5);
    }
}
1.6 使用Unsafe类创建对象

我们无法直接创建Unsafe对象。这里我们使用反射方法得到

private static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
            return unsafe;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
  }

拿到这个对象后,调用其中的native方法allocateInstance 创建一个对象实例

Object event = unsafe.allocateInstance(Test.class);

2 从java角度看对象的创建

创建一个对象包含下面两个过程

  • 1、类初始化

  • 2、类实例化

public class InitialTest {
    //静态变量
    public static String staticField = "静态变量";
    //变量
    public String field = "==普通变量==";
    // 静态代码块
    static {
        System.out.println( staticField );
        System.out.println( "静态初始化块" );
    }
    // 初始化块
    {
        System.out.println( field );
        System.out.println( "==初始化块==" );
    }
    // 构造方法
    public InitialTest(){
        System.out.println( "构造器" );
    }

    public static void main( String[] args ){
        new InitialTest();
    }
}

静态变量
静态初始化块
==普通变量==
==初始化块==
构造器

如果这个类包含父类

  • 1、父类初始化(按顺序执行父类静态变量初始化,父类静态代码块执行)
  • 2、类初始化(按顺序执行类静态变量初始化,类静态代码块执行)
  • 3、父类实例化 (按顺序执行父类成员属性初始化,父类方法块执行,最后执行父类构造函数)
  • 4、类实例化(按顺序执行类成员属性初始化,类方法块块执行,最后执行类构造函数)
package jvm;

class FatherClass {
    // 静态变量
    public static String parent_StaticField = "父----静态变量";
    // 变量
    public String parent_Field = "父类----普通变量";
    // 静态初始化块
    static {
        System.out.println(parent_StaticField);
        System.out.println("父类------静态初始化块");
    }
    // 初始化块
    {
        System.out.println(parent_Field);
        System.out.println("父类-----初始化块");
    }
    // 构造器
    public FatherClass() {
        System.out.println("父类--构造器");
    }
}

public class SubSon extends FatherClass  {
    // 静态变量
    public static String son_StaticField = "子类--静态变量";
    // 变量
    public String son_Field = "子类--变量";
    // 静态初始化块
    static {
        System.out.println(son_StaticField);
        System.out.println("子类--静态初始化块");
    }
    // 初始化块
    {
        System.out.println(son_Field);
        System.out.println("子类--初始化块");
    }
    // 构造器
    public SubSon(){
        System.out.println( "子类--构造器" );
    }
    public static void main(String[] args) {
        System.out.println("子类-----main方法");
        new SubSon();
    }
}


父----静态变量
父类------静态初始化块
子类--静态变量
子类--静态初始化块
子类-----main方法
父类----普通变量
父类-----初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器

3 从jvm角度看对象的创建

Java字节码指令是通过类加载器将.class文件中数据加载到JVM方法区中,虚拟机执行Java字节码指令来创建对象

InitialTest字节码指令

Classfile /C:/work/project/juc-in-action/target/classes/jvm/InitialTest.class
  Last modified 2019-8-3; size 848 bytes
  MD5 checksum e162c42c96603ca340a211307163c1b6
  Compiled from "InitialTest.java"
public class jvm.InitialTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #13.#31        // java/lang/Object."<init>":()V
   #2 = String             #32            // ==普通变量==
   #3 = Fieldref           #8.#33         // jvm/InitialTest.field:Ljava/lang/String;
   #4 = Fieldref           #34.#35        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Methodref          #36.#37        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = String             #38            // ==初始化块==
   #7 = String             #39            // 构造器
   #8 = Class              #40            // jvm/InitialTest
   #9 = Methodref          #8.#31         // jvm/InitialTest."<init>":()V
  #10 = String             #41            // 静态变量
  #11 = Fieldref           #8.#42         // jvm/InitialTest.staticField:Ljava/lang/String;
  #12 = String             #43            // 静态初始化块
  #13 = Class              #44            // java/lang/Object
  #14 = Utf8               staticField
  #15 = Utf8               Ljava/lang/String;
  #16 = Utf8               field
  #17 = Utf8               <init>
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               this
  #23 = Utf8               Ljvm/InitialTest;
  #24 = Utf8               main
  #25 = Utf8               ([Ljava/lang/String;)V
  #26 = Utf8               args
  #27 = Utf8               [Ljava/lang/String;
  #28 = Utf8               <clinit>
  #29 = Utf8               SourceFile
  #30 = Utf8               InitialTest.java
  #31 = NameAndType        #17:#18        // "<init>":()V
  #32 = Utf8               ==普通变量==
  #33 = NameAndType        #16:#15        // field:Ljava/lang/String;
  #34 = Class              #45            // java/lang/System
  #35 = NameAndType        #46:#47        // out:Ljava/io/PrintStream;
  #36 = Class              #48            // java/io/PrintStream
  #37 = NameAndType        #49:#50        // println:(Ljava/lang/String;)V
  #38 = Utf8               ==初始化块==
  #39 = Utf8               构造器
  #40 = Utf8               jvm/InitialTest
  #41 = Utf8               静态变量
  #42 = NameAndType        #14:#15        // staticField:Ljava/lang/String;
  #43 = Utf8               静态初始化块
  #44 = Utf8               java/lang/Object
  #45 = Utf8               java/lang/System
  #46 = Utf8               out
  #47 = Utf8               Ljava/io/PrintStream;
  #48 = Utf8               java/io/PrintStream
  #49 = Utf8               println
  #50 = Utf8               (Ljava/lang/String;)V
{
  public static java.lang.String staticField;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC

  public java.lang.String field;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC

  public jvm.InitialTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String ==普通变量==
         7: putfield      #3                  // Field field:Ljava/lang/String;
        10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: aload_0
        14: getfield      #3                  // Field field:Ljava/lang/String;
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        23: ldc           #6                  // String ==初始化块==
        25: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        28: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        31: ldc           #7                  // String 构造器
        33: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        36: return
      LineNumberTable:
        line 19: 0
        line 7: 4
        line 15: 10
        line 16: 20
        line 20: 28
        line 21: 36
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      37     0  this   Ljvm/InitialTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #8                  // class jvm/InitialTest
         3: dup
         4: invokespecial #9                  // Method "<init>":()V
         7: pop
         8: return
      LineNumberTable:
        line 24: 0
        line 25: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: ldc           #10                 // String 静态变量
         2: putstatic     #11                 // Field staticField:Ljava/lang/String;
         5: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: getstatic     #11                 // Field staticField:Ljava/lang/String;
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: ldc           #12                 // String 静态初始化块
        19: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        22: return
      LineNumberTable:
        line 5: 0
        line 10: 5
        line 11: 14
        line 12: 22
}
SourceFile: "InitialTest.java"
3.1 执行new指令
  • new指令用来创建一个对象并为其分配内存空间,并将创建对象的引用推到操作数栈中。创建对象用常量池中第#10常量来表示,该常量类型是一个类的符号引用。这里创建一个jvm/InitialTest对象。

  • new指令内部除了完成对象分配内存外,还可能触发 类加载

3.1.1 类加载

如果new指令创建对象类并没有被加载。会触发类的加载机制加载InitialTest类,这其中包括了加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)等过程,详见Java虚拟机-类加载机制如果加载类存在父类,则优先加载父类,在加载子类

JVM调用<init>函数完成对象初始化,会将<init>符号引用常量转化为直接引用。直接引用内保存了init函数在方法区中内存地址。这里对应到字节码中 {}。接下来会Code属性第一个要执行的字节码指令位置,开始执行。<clinit>类的初始化包含了对类静态变量的初始化和类静态代码块的执行。

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: ldc           #10                 // String 静态变量
         2: putstatic     #11                 // Field staticField:Ljava/lang/String;
         5: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: getstatic     #11                 // Field staticField:Ljava/lang/String;
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: ldc           #12                 // String 静态初始化块
        19: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        22: return

静态变量staticField初始化

如下指定完成了静态变量staticField初始化

0: ldc           #10                 // String 静态变量
2: putstatic     #11                 // Field staticField:Ljava/lang/String;

//对应Java源文件代码
public static String staticField = "静态变量";
  • 1 ldc:ldc指令用来将常量池中指定的常量放入操作数栈中,这里指定的常量是#10,常量类型是String,其值#41指向一个类型为utf-8的常量,对应值为"静态变量"

  • 2 putstatic:putstatic指令用来将操作数栈顶的元素(1步骤入栈元素)赋值给指定静态变量,并将栈顶元素从操作数栈中出栈。这里指定静态变量用常量池中第#11常量来表示,该常量类型是一个字段的符号引用。这里用来对类staticField静态变量初始化。

打印静态变量staticField

如下指令用来执行静态代码块中第一行代码:打印静态变量staticField

5: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
8: getstatic     #11                 // Field staticField:Ljava/lang/String;
11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

 static {
        System.out.println( staticField );
        ...
    }
  • 3 getstatic:getstatic指令用来表示获取一个类的静态变量引用,并将其引用推到操作数栈中。指令获取静态变量用常量池中第#4常量来表示,该常量类型是一个字段的符号引用。这里获取的是System类中PrintStream字段引用放入栈中。

  • 4 getstatic:getstatic指令用来表示获取一个类的静态变量引用,并将其引用推到操作数栈中。指令获取静态变量用常量池中第#11常量来表示,该常量类型是一个字段的符号引用。这里获取的是InitialTest类中staticField字段引用放入栈中,对应值为"静态变量"。

  • 5 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#5常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里含义是调用PrintStream对象println方法,由于方法中存在一个参数,此时会将栈顶的元素(4步骤入栈元素)作为参数,其值指向对象field成员变量引用,并从操作数中出栈,接着将新的栈顶元素作为方法调用对象(3步骤入栈元素),并从操作数中出栈。由于println方法不存在返回值,因而不存在入栈操作。

打印常量

如下指令用来执行静态代码块中第二行代码:打印常量

14: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
17: ldc           #12                 // String 静态初始化块
19: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

static {
        ...
        System.out.println( "静态初始化块" );
}
    }
  • 6 getstatic:getstatic指令用来表示获取一个类的静态变量引用,并将其引用推到操作数栈中。指令获取静态变量用常量池中第#4常量来表示,该常量类型是一个字段的符号引用。这里获取的是System类中PrintStream字段引用放入栈中。

  • 7 ldc:ldc指令用来将常量池中指定的常量放入操作数栈中,这里指定的常量是#12,常量类型是String,其值#41指向一个类型为utf-8的常量,对应值为”静态初始化块“

  • 8 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#5常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里含义是调用PrintStream对象println方法,由于方法中存在一个参数,此时会将栈顶的元素(7步骤入栈元素)作为参数,其值指向对象field成员变量引用,并从操作数中出栈,接着将新的栈顶元素作为方法调用对象(6步骤入栈元素),并从操作数中出栈。由于println方法不存在返回值,因而不存在入栈操作。

返回

22: return
  • 9 retrun:返回。到此完成了类的加载和初始化。
3.1.2 分配内存

3.1.2.1 堆中内存结构

  • 在Java中对象内存的分配发生在堆中,而堆通常是gc管理的主要区域。

  • 在堆中JVM会按对象按照对象存活寿命长短将堆划分为新生代和老年代2个区域。新生代存放最近new出来的对象,老年代存放长期存活的对象。如果一个对象在经历了多次GC回收并存活,会将对象从新生代晋升移动到老年代中。

  • JVM会按照不同年代区域使用不同垃圾回收算法。在新生代采用复制算法,在老年代采用“标记-清除

复制算法

复制算法将内存划分为相等的两块,每次只使用其中一块。当这一块内存用完时,就将还存活的对象复制到另一块上面,然后将已经使用过的内存空间一次清理掉。

image

标记-清除算法

该垃圾收集算法主要分成”标记“和”清除“两个阶段:首先标记出所有需要回收的对象,而后在标记完成后统一回收所有被标记的对象。


image

3.1.2.2 分配规则

由于对象即可能分配在新生代,也可能分配到老年代。且不同区域使用不同回收方式,导致内存结构不同,因而需要使用不同方式来分配对象内存。

在新生代堆内存规整,使用指针碰撞,在老年代堆内存不规整,使用空闲列表

image

指针碰撞
在java堆中,将已用内存和未用的内存分开成两部分,两部分内存之间放这一个指针作为分界点,当有新的实例对象需要分配内存空间时,指针向未用内存一侧移动相应大小的距离,将新的实例对象存储在该内存空间上。

image

空闲列表

使用一个列表记录内存中可以存放数据内存空间地址,每当有实例对象需要空间,就从这个列表中选取出一块内存分配给实例对象

image

3.1.2.3 内存分配多线程问题

Java程序是支持多线程操作。而分配内存区域堆是多个线程共享的。这样在分配内存时就可能存在多线程问题,解决这种问题主要有两种办法:

  • 一:对创建对象动作行为进行同步处理,这种同步处理实质是CAS+for循环的方式,保证更新操作的原子性的。

  • 二:为每一个线程分配一小块内存空间(本地线程分配缓冲TLAB),每个线程要分配内存就在自己的TLAB上运行分配,只有当TLAB满了,需要重新分配TLAB时(线程同步)。
    TLAB(Thread Local Allocation Buffer)方式的开启需要通过-XX:+/-UseTLAB参数设定。

image

3.1.2.4 对象的内存布局

对象在 Java 内存中的 存储布局 可分为三块

  • 对象头 存储区域
  • 实例数据 存储区域
  • 对齐填充 存储区域

[图片上传失败...(image-ab61d5-1564897879252)]

3.1.3 对象内存的初始化

对象被内存分配完成后,需要将内存中数据做初始化。

  • 对于成员变量为基本类型的数据设置一个默认值。
  • 对于成员变量为引用类型的数据会设置默认地址。
//变量value在对象内存的初始化后的值为0而不是123
public int value = 123;
3.1.4 字段内存的初始化

对象被内存分配完成后,如果字段中存在引用类型成员变量,需要将其指向地址对象进行内存分配

//对Object对象进行内存分配
public Object ref = new Object;
3.1.5 设置对象头部信息

对象内存初始化完毕后,会用来设置对象中头部信息。

到此new指定终于结束了

3.2 执行dup指令

dup:dup指令复制操作数栈栈顶的值,并插入到栈顶。当前的栈顶元素为新建的InitialTest对象的引用。【调用方法指令前,需要将调用方法的对象加入到操作数栈中。此步骤是调用构造函数的一个中间步骤】

3.3 执行invokespecial指令

invokespecial:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#9常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里符号引用常量描述的是对象<init>的初始化函数。由于<init>的初始化函数不存在参数和返回值,只会将当前栈顶元素(步骤3.2入栈对象)作为方法调用的对象并出栈。

JVM调用<init>函数完成对象初始化,会将<init>符号引用常量转化为直接引用。直接引用内保存了init函数在方法区中内存地址。这里对应到字节码中jvm.InitialTest()函数。接下来会Code属性第一个要执行的字节码指令位置,开始执行。

<init>函数中首先会调用父类对象的<init>,之后对象成员变量的赋值/代码块的执行(顺序执行)。最后调用对象的构造函数。

 public jvm.InitialTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String ==普通变量==
         7: putfield      #3                  // Field field:Ljava/lang/String;
        10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: aload_0
        14: getfield      #3                  // Field field:Ljava/lang/String;
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        23: ldc           #6                  // String ==初始化块==
        25: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        28: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        31: ldc           #7                  // String 构造器
        33: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        36: return

父类对象的初始化

如下指令用来调用父类对象<init>初始化函数。

 0: aload_0
 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
  • 1 aload_0:将局部变量表中第一个局部变量的引用推到栈顶。在Java虚拟机中对于非静态方法调用,会将当前对象引用放入到局部变量表中第一个局部变量位置。【调用方法指令前,需要将调用方法的对象加入到操作数栈中。此步骤是调用构造函数的一个中间步骤】

  • 2 invokespecial:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#9常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里符号引用常量#1描述的是父类Methodjava/lang/Object."<init>":()V的初始化函数。由于<init>的初始化函数不存在参数和返回值,只会将当前栈顶元素(步骤1入栈对象)作为方法调用的对象并出栈。

成员变量field的初始化

如下指令用来将成员变量field的初始化。

 4: aload_0
 5: ldc           #2                  // String ==普通变量==
 7: putfield      #3                  // Field field:Ljava/lang/String;
 
//对应Java源文件代码
 public String field = "普通变量";
  • 3 aload_0:将局部变量表中第一个局部变量的引用推到栈顶。在Java虚拟机中对于非静态方法调用,会将当前对象引用放入到局部变量表中第一个局部变量位置。【调用方法指令前,需要将调用方法的对象加入到操作数栈中。此步骤是调用构造函数的一个中间步骤】

  • 4 ldc:将常量池中#2常量引用推入操作数栈。该常量是一个String类型的常量,其值#32指向utf-8字面量对应为字符串"==普通变量=="。

  • 5 putfield:putfield指令用来将操作数栈顶的元素(4步骤入栈元素)赋值给指定成员变量,并从操作数栈中出栈。同时将新的栈顶元素(3步骤入栈元素)作为赋值成员变量所在的对象,并从操作数栈中出栈。这里指定成员变量用常量池中第#3常量来表示,该常量类型是一个字段的符号引用。这里获取的是类对象中field成员变量。

打印成员变量值

如下指令用来执行代码块中第一行代码:打印成员变量field。

10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: getfield      #3                  // Field field:Ljava/lang/String;
17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

//对应Java源文件代码
{
  System.out.println( field );
  ...
}
  • 6 getstatic:getstatic指令用来表示获取一个类的静态变量引用,并将其引用推到操作数栈中。指令获取静态变量用常量池中第#4常量来表示,该常量类型是一个字段的符号引用。这里获取的是System类中PrintStream字段引用放入栈中。

  • 7 aload_0:将局部变量表中第一个局部变量的引用推到栈顶。在Java虚拟机中对于非静态方法调用,会将当前对象引用放入到局部变量表中第一个局部变量位置。【调用方法指令前,需要将调用方法的对象加入到操作数栈中。此步骤是调用构造函数的一个中间步骤】

  • 8 getfield:getfield指令用来获取栈顶元素(7步骤入栈元素)的指定成员变量,在此会将栈顶元素(7步骤入栈元素)出栈,并将获取成员变量入栈。这里指定成员变量用常量池中第#3常量来表示,该常量类型是一个字段的符号引用。这里用来获取当前对象this.field成员变量,将this对象出栈,将成员变量入栈。

  • 9 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#5常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里含义是调用PrintStream对象println方法,由于方法中存在一个参数,此时会将栈顶的元素(8步骤入栈元素)作为参数,其值指向对象field成员变量引用,并从操作数中出栈,接着将新的栈顶元素作为方法调用对象(6步骤入栈元素),并从操作数中出栈。由于println方法不存在返回值,因而不存在入栈操作。

打印常量'==初始化块=='

如下指令用来执行代码块中第二行代码:打印常量'==初始化块=='。

20: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
23: ldc           #6                  // String ==初始化块==
25: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

//对应Java源文件代码
{
  ...
  System.out.println( "==初始化块==" );
}
  • 10 getstatic:getstatic指令用来表示获取一个类的静态变量引用,并将其引用推到操作数栈中。指令获取静态变量用常量池中第#4常量来表示,该常量类型是一个字段的符号引用。这里获取的是System类中PrintStream字段引用放入栈中。

  • 11 4 ldc:将常量池中#7常量引用推入操作数栈,该常量是一个String类型的常量。其值#32指向utf-8字面量对应为字符串"=初始化块=="

  • 12 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#5常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里含义是调用PrintStream对象println方法,由于方法中存在一个参数,此时会将栈顶的元素(11步骤入栈元素)作为参数,其值指向对象field成员变量引用,并从操作数中出栈,接着将新的栈顶元素作为方法调用对象(10步骤入栈元素),并从操作数中出栈。由于println方法不存在返回值,因而不存在入栈操作。

打印常量'构造器'

如下指令用来执行代码块中代码:打印常量'构造器'。

28: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
31: ldc           #7                  // String 构造器
33: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

public InitialTest(){
    System.out.println( "构造器" );
}    
  • 13 getstatic:getstatic指令用来表示获取一个类的静态变量引用,并将其引用推到操作数栈中。指令获取静态变量用常量池中第#4常量来表示,该常量类型是一个字段的符号引用。这里获取的是System类中PrintStream字段引用放入栈中。

  • 14 ldc:将常量池中#6常量引用推入操作数栈,该常量是一个String类型的常量。其值#39指向utf-8字面量对应为字符串"构造器"

  • 15 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#5常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里含义是调用PrintStream对象println方法,由于方法中存在一个参数,此时会将栈顶的元素(14步骤入栈元素)作为参数,其值指向对象field成员变量引用,并从操作数中出栈,接着将新的栈顶元素作为方法调用对象(13步骤入栈元素),并从操作数中出栈。由于println方法不存在返回值,因而不存在入栈操作。

返回

return 

总结

对象的创建过程.jpg

对象的创建过程.jpg

相关文章

网友评论

      本文标题:Java虚拟机-如何创建对象

      本文链接:https://www.haomeiwen.com/subject/bqqddctx.html