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堆中,将已用内存和未用的内存分开成两部分,两部分内存之间放这一个指针作为分界点,当有新的实例对象需要分配内存空间时,指针向未用内存一侧移动相应大小的距离,将新的实例对象存储在该内存空间上。
空闲列表
使用一个列表记录内存中可以存放数据内存空间地址,每当有实例对象需要空间,就从这个列表中选取出一块内存分配给实例对象
image3.1.2.3 内存分配多线程问题
Java程序是支持多线程操作。而分配内存区域堆是多个线程共享的。这样在分配内存时就可能存在多线程问题,解决这种问题主要有两种办法:
-
一:对创建对象动作行为进行同步处理,这种同步处理实质是CAS+for循环的方式,保证更新操作的原子性的。
-
二:为每一个线程分配一小块内存空间(本地线程分配缓冲TLAB),每个线程要分配内存就在自己的TLAB上运行分配,只有当TLAB满了,需要重新分配TLAB时(线程同步)。
TLAB(Thread Local Allocation Buffer)方式的开启需要通过-XX:+/-UseTLAB参数设定。
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
网友评论