java 對象不存在_Java虚拟机-如何创建对象

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 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 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 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."":()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."":()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

#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

#29 = Utf8 SourceFile

#30 = Utf8 InitialTest.java

#31 = NameAndType #17:#18 // "":()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."":()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 "":()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函数在方法区中内存地址。这里对应到字节码中 {}。接下来会Code属性第一个要执行的字节码指令位置,开始执行。类的初始化包含了对类静态变量的初始化和类静态代码块的执行。

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指令用来调用超类构造方法,实例初始化方法,私有方法。和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指令用来调用超类构造方法,实例初始化方法,私有方法。和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会按照不同年代区域使用不同垃圾回收算法。在新生代采用复制算法,在老年代采用“标记-清除”

复制算法

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

f874cd27d61d

image

标记-清除算法

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

f874cd27d61d

image

3.1.2.2 分配规则

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

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

f874cd27d61d

image

指针碰撞

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

f874cd27d61d

image

空闲列表

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

f874cd27d61d

image

3.1.2.3 内存分配多线程问题

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

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

二:为每一个线程分配一小块内存空间(本地线程分配缓冲TLAB),每个线程要分配内存就在自己的TLAB上运行分配,只有当TLAB满了,需要重新分配TLAB时(线程同步)。

TLAB(Thread Local Allocation Buffer)方式的开启需要通过-XX:+/-UseTLAB参数设定。

f874cd27d61d

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

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

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

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."":()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

父类对象的初始化

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

0: aload_0

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

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

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

返回

return

总结

f874cd27d61d

对象的创建过程.jpg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值