美文网首页
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