深入java虚拟机--Class文件实例解析

时间:2021-08-19 17:16:49

  前面发了几篇学习笔记,但是看这些东西总是感觉很"玄乎",来一篇实战的东西来揭一下"JVM"的面纱,让"SSH"时代的童鞋们来熟悉一下Java"老祖爷"JVM。由于自己的水平有限,所以大家在看过程中发了什么问题,或者您有什么疑问请及时提出来,我及时解决。如果您有什么建议,那么更好大家一块讨论。

  1. 源码文件

public class LearningClassFile {
//普通变量
private int id1;
//静态变量
private static int id2;
//常量
private final int id3 = 4;
//静态常量
private static final int id4 = 5;

public LearningClassFile() {
}

public LearningClassFile(int id1, int id2) {
this.id1 = id1;
this.id2 = id2;
}

//使用public修饰的addPub方法
public void addPub(int a, int b) {
int result = a + b;
System.out.println(result);
}

//使用private修饰的addPri方法
private void addPri(int a, int b) {
int result = a + b;
System.out.println(result);
}

//使用static修饰的方法
public static void addSta() {
int result = id2 + id4;
System.out.println(result);
}

public static final void addFinal(int a, int b) {
int result = a + b;
System.out.println(result);
}

public static void main(String[] args) {
LearningClassFile lcf = new LearningClassFile(1, 2);
lcf.addPub(1, 2);
lcf.addPri(1, 2);
addSta();
addFinal(1, 2);
}
}

 Class文件:

 

	Compiled from "LearningClassFile.java"
public class LearningClassFile extends java.lang.Object
SourceFile: "LearningClassFile.java"
minor version: 0
major version: 50
//运行时常量池:用于存放编译期生成的各种字面量和符号引用。
Constant pool:
//从父类Object继承的默认构造方法
//观察该方法的特征:无参,返回类型void
const #1 = Method #13.#35; // java/lang/Object."<init>":()V
//常量id3
//"#7.#36; // LearningClassFile.id3:I"
//#7:查找常量池中的类名LearningClassFile
//#36-->"const #36 = NameAndType #17:#15;// id3:I"
//NameAndType字面的意思是名称和类型。即id3是变量的名称,I表示id3是int类型
//综合描述:LearningClassFile中的id3是int类型
const #2 = Field #7.#36; // LearningClassFile.id3:I
const #3 = Field #7.#37; // LearningClassFile.id1:I
const #4 = Field #7.#38; // LearningClassFile.id2:I
//将System的out存储至常量池
//System类中out被public static final修饰的
//"public final static PrintStream out = nullPrintStream();"
//综合描述:System类的out属性是PrintStream类型
const #5 = Field #39.#40; // java/lang/System.out:Ljava/io/PrintS
tream;
//将PrintStream的Println()方法存储至常量池
//该方法的参数为I,返回值为void
const #6 = Method #41.#42; // java/io/PrintStream.println:(I)V
//类LearningClassFIle
const #7 = class #43; // LearningClassFile
//构造函数
//该构造函数需传入两个int类型的变量
const #8 = Method #7.#44; // LearningClassFile."<init>":(II)V
//LearningClassFile的addPub方法
//#4-->"const #45 = NameAndType #27:#26;// addPub:(II)V"
//#27-->"const #27 = Asciz addPub;" 方法的名称为:addPub
//#26-->"const #26 = Asciz (II)V;" 方法的类型:两个int类型的参数,返回类型为void
const #9 = Method #7.#45; // LearningClassFile.addPub:(II)V
const #10 = Method #7.#46; // LearningClassFile.addPri:(II)V
const #11 = Method #7.#47; // LearningClassFile.addSta:()V
const #12 = Method #7.#48; // LearningClassFile.addFinal:(II)V
const #13 = class #49; // java/lang/Object
const #14 = Asciz id1;
const #15 = Asciz I;
const #16 = Asciz id2;
const #17 = Asciz id3;
//ConstantValue属性表示一个常量字段的值
//即final修饰的属性
const #18 = Asciz ConstantValue;
//对于final修饰的常量直接将类型和值存入常量池
const #19 = int 4;
const #20 = Asciz id4;
const #21 = int 5;
const #22 = Asciz <init>;
const #23 = Asciz ()V;
//Code属性只为唯一一个方法、实例类初始化方法或类初始化方法保存Java虚拟机指令及相关辅助信息
//简而言之:保存方法编译后的指令信息
const #24 = Asciz Code;
//java源码行号与编译后的字节码指令的对应表
const #25 = Asciz LineNumberTable;
const #26 = Asciz (II)V;
const #27 = Asciz addPub;
const #28 = Asciz addPri;
const #29 = Asciz addSta;
const #30 = Asciz addFinal;
const #31 = Asciz main;
const #32 = Asciz ([Ljava/lang/String;)V;
//java 源码文件
const #33 = Asciz SourceFile;
const #34 = Asciz LearningClassFile.java;
const #35 = NameAndType #22:#23;// "<init>":()V
const #36 = NameAndType #17:#15;// id3:I
const #37 = NameAndType #14:#15;// id1:I
const #38 = NameAndType #16:#15;// id2:I
const #39 = class #50; // java/lang/System
const #40 = NameAndType #51:#52;// out:Ljava/io/PrintStream;
const #41 = class #53; // java/io/PrintStream
const #42 = NameAndType #54:#55;// println:(I)V
const #43 = Asciz LearningClassFile;
const #44 = NameAndType #22:#26;// "<init>":(II)V
const #45 = NameAndType #27:#26;// addPub:(II)V
const #46 = NameAndType #28:#26;// addPri:(II)V
const #47 = NameAndType #29:#23;// addSta:()V
const #48 = NameAndType #30:#26;// addFinal:(II)V
const #49 = Asciz java/lang/Object;
const #50 = Asciz java/lang/System;
const #51 = Asciz out;
const #52 = Asciz Ljava/io/PrintStream;;
const #53 = Asciz java/io/PrintStream;
const #54 = Asciz println;
const #55 = Asciz (I)V;

{
//默认构造方法
public LearningClassFile();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
//将id3的引用推送至栈顶
4: aload_0
//将4推送至栈顶
5: iconst_4
//将4赋值给id3
6: putfield #2; //Field id3:I
9: return
LineNumberTable:
line 11: 0 //public LearningClassFile() {
//对于final类型的实例变量在每个构造方法中都会进行一次初始化。
line 7: 4 // private final int id3 = 4;
line 12: 9 //}


public LearningClassFile(int, int);
Code:
Stack=2, Locals=3, Args_size=3
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_4
6: putfield #2; //Field id3:I
9: aload_0
10: iload_1
11: putfield #3; //Field id1:I
14: aload_0
15: pop
16: iload_2
17: putstatic #4; //Field id2:I
20: return
LineNumberTable:
line 14: 0 //public LearningClassFile(int id1, int id2) {
//对于final类型的实例变量在每个构造方法中都会进行一次初始化。
line 7: 4 // private final int id3 = 4;
line 15: 9 // this.id1 = id1;
line 16: 14 // this.id2 = id2;
line 17: 20 //}


public void addPub(int, int);
Code:
Stack=2, Locals=4, Args_size=3
0: iload_1
1: iload_2
2: iadd
3: istore_3
4: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_3
8: invokevirtual #6; //Method java/io/PrintStream.println:(I)V
11: return
LineNumberTable:
line 21: 0 // int result = a + b;
line 22: 4 // System.out.println(result);
line 23: 11 // }


public static void addSta();
Code:
Stack=2, Locals=1, Args_size=0
//获取静态变量id2推送至栈顶
0: getstatic #4; //Field id2:I
//直接从常量池中取出id4的值5推送至栈顶
3: iconst_5
//执行相加操作
4: iadd
//将计算结果推送至栈顶
5: istore_0
//获取静态与out
6: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
//取出计算结果
9: iload_0
//调用println方法
10: invokevirtual #6; //Method java/io/PrintStream.println:(I)V
//方法正常结束
13: return
LineNumberTable:
line 33: 0 // int result = id2 + id4;
line 34: 6 // System.out.println(result);
line 35: 13 //}


public static final void addFinal(int, int);
Code:
Stack=2, Locals=3, Args_size=2
0: iload_0
1: iload_1
2: iadd
3: istore_2
4: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_2
8: invokevirtual #6; //Method java/io/PrintStream.println:(I)V
11: return
LineNumberTable:
line 38: 0
line 39: 4
line 40: 11


public static void main(java.lang.String[]);
Code:
Stack=4, Locals=2, Args_size=1
//创建一个LearningClassFile对象,并将对象的引用推送至栈顶
0: new #7; //class LearningClassFile
//将对象的引用进行备份推送至栈顶
//使用原有的引用值调用实例方法,现在置于栈顶的引用值的位置将被接下来的操作覆盖。
3: dup
//将构造函数中的参数1推送至栈顶
4: iconst_1
5: iconst_2
//执行构造方法
6: invokespecial #8; //Method "<init>":(II)V
//将栈顶引用型数值存入第二个本地变量
9: astore_1
10: aload_1
11: iconst_1
12: iconst_2
//调用实例方法
13: invokevirtual #9; //Method addPub:(II)V
16: aload_1
17: iconst_1
18: iconst_2
19: invokespecial #10; //Method addPri:(II)V
//调用静态方法
22: invokestatic #11; //Method addSta:()V
25: iconst_1
26: iconst_2
27: invokestatic #12; //Method addFinal:(II)V
30: return
LineNumberTable:
line 43: 0 // LearningClassFile lcf = new LearningClassFile(1, 2);
line 44: 10 // lcf.addPub(1, 2);
line 45: 16 // lcf.addPri(1, 2);
line 46: 22 // addSta();
line 47: 25 // addFinal(1, 2);
line 48: 30 //}
}
final变量和static final变量的区别:
1. 实例常量和类常量的区别
2. 初识方式不同:从class字节码来看final修饰的变量会出现在每个构造方法中进行一次初始化;static final类型的变量必须在定义的时候进行初始化。

理解"编译期可知,运行期不变": 编译器可确定调用方法的版本,符合这个标准的方法主要有两种:私有方法,静态方法。详情请看:深入理解JVM读书笔记--字节码执行引擎。

  2. final变量和static final变量的区别: (1) 实例常量和类常量的区别 (2) 初始化方式不同:从class字节码来看final修饰的变量会出现在每个构造方法中进行一次初始化;static final类型的变量必须在定义的时候进行初始化。

  3. 理解"编译期可知,运行期不变": 编译器可确定调用方法的版本,符合这个标准的方法主要有两种:私有方法,静态方法。详情请看:深入理解JVM读书笔记--字节码执行引擎