对Java字节码文件到JVM运行的一个认识
推荐书籍:java虚拟机规范 这本书告诉我们: JVM是一种规范深入理解java虚拟机先有个整体的概念:java程序运行的本质就是方法套方法,我们需要知道的就是我们编写的java文件编译后的字节码被JVM如何保存(即如何把一个类的各种信息存下来),在方法执行的时候如何能找到之前存的信息,还有在执行过程中发生了什么(内存中如何变化)一、JVM如何保存字节码文件1. Java代码编译成的字节码文件:先
推荐书籍:
java虚拟机规范 这本书告诉我们: JVM是一种规范
深入理解java虚拟机
先有个整体的概念:java程序运行的本质就是方法套方法,我们需要知道的就是我们编写的java文件编译后的字节码被JVM如何保存(即如何把一个类的各种信息存下来),在方法执行的时候如何能找到之前存的信息,还有在执行过程中发生了什么(内存中如何变化)
字节码如何被JVM保存?
被保存在方法区,主要是分成:访问权限和类的属性(access_flags)、类索引父类索引和接口索引集合、常量池、字段表、方法表、和字段和方法结构中的属性表(方法的内容字节码就是存在属性表的code[]属性中)。 可以理解成Class的结构就是一张表,在执行的时候提供需要的信息
在方法执行的时候如何找到这些信息?
在编译的时候所有在本Class文件中用到的字段和调用的方法(只要有方法调用就行例如上面的setAge中的getName方法),符号引用都会被保存在字节码的常量池中(包括其他类的也会存在自己的常量池表中),类加载后JVM会为每一个类都维护一个自己的常量池,在类加载的解析阶段,这些符号引用就被解析成了内存地址直接引用。
这里肯定会有一个疑问那那些没有用到的方法和字段如何查找呢?
可以通过Class对象
一、JVM如何保存字节码文件
1. Java代码编译成的字节码文件:
先看一个类:
public class TestA {
public static String gender;
public int age;
public String name;
public static String getGender() {
return gender;
}
public static void setGender(String gender) {
TestA.gender = gender;
}
public final int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
getName();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
TestA testA = new TestA();
testA.setAge(2);
}
}
再看字节码文件:
Last modified 2022-1-7; size 999 bytes
MD5 checksum 56933a64868cb691926b1cc349d82d08
Compiled from "TestA.java"
public class com.example.fragmentadaptertest.testjava.TestA
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#35 // java/lang/Object."<init>":()V
#2 = Fieldref #6.#36 // com/example/fragmentadaptertest/testjava/TestA.gender:Ljava/lang/String;
#3 = Fieldref #6.#37 // com/example/fragmentadaptertest/testjava/TestA.age:I
#4 = Methodref #6.#38 // com/example/fragmentadaptertest/testjava/TestA.getName:()Ljava/lang/String;
#5 = Fieldref #6.#39 // com/example/fragmentadaptertest/testjava/TestA.name:Ljava/lang/String;
#6 = Class #40 // com/example/fragmentadaptertest/testjava/TestA
#7 = Methodref #6.#35 // com/example/fragmentadaptertest/testjava/TestA."<init>":()V
#8 = Methodref #6.#41 // com/example/fragmentadaptertest/testjava/TestA.setAge:(I)V
#9 = Class #42 // java/lang/Object
#10 = Utf8 gender
#11 = Utf8 Ljava/lang/String;
#12 = Utf8 age
#13 = Utf8 I
#14 = Utf8 name
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 LocalVariableTable
#20 = Utf8 this
#21 = Utf8 Lcom/example/fragmentadaptertest/testjava/TestA;
#22 = Utf8 getGender
#23 = Utf8 ()Ljava/lang/String;
#24 = Utf8 setGender
#25 = Utf8 (Ljava/lang/String;)V
#26 = Utf8 getAge
#27 = Utf8 ()I
#28 = Utf8 setAge
#29 = Utf8 (I)V
#30 = Utf8 getName
#31 = Utf8 setName
#32 = Utf8 testA
#33 = Utf8 SourceFile
#34 = Utf8 TestA.java
#35 = NameAndType #15:#16 // "<init>":()V
#36 = NameAndType #10:#11 // gender:Ljava/lang/String;
#37 = NameAndType #12:#13 // age:I
#38 = NameAndType #30:#23 // getName:()Ljava/lang/String;
#39 = NameAndType #14:#11 // name:Ljava/lang/String;
#40 = Utf8 com/example/fragmentadaptertest/testjava/TestA
#41 = NameAndType #28:#29 // setAge:(I)V
#42 = Utf8 java/lang/Object
{
public static java.lang.String gender;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
public int age;
descriptor: I
flags: ACC_PUBLIC
public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
public com.example.fragmentadaptertest.testjava.TestA();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/fragmentadaptertest/testjava/TestA;
public static java.lang.String getGender();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #2 // Field gender:Ljava/lang/String;
3: areturn
LineNumberTable:
line 13: 0
public static void setGender(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: putstatic #2 // Field gender:Ljava/lang/String;
4: return
LineNumberTable:
line 17: 0
line 18: 4
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 gender Ljava/lang/String;
public final int getAge();
descriptor: ()I
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #3 // Field age:I
4: ireturn
LineNumberTable:
line 21: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/fragmentadaptertest/testjava/TestA;
public void setAge(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #3 // Field age:I
5: aload_0
6: invokevirtual #4 // Method getName:()Ljava/lang/String;
9: pop
10: return
LineNumberTable:
line 25: 0
line 26: 5
line 27: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/example/fragmentadaptertest/testjava/TestA;
0 11 1 age I
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #5 // Field name:Ljava/lang/String;
4: areturn
LineNumberTable:
line 30: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/fragmentadaptertest/testjava/TestA;
public void setName(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=2
0: aload_0
1: aload_1
2: putfield #5 // Field name:Ljava/lang/String;
5: new #6 // class com/example/fragmentadaptertest/testjava/TestA
8: dup
9: invokespecial #7 // Method "<init>":()V
12: astore_2
13: aload_2
14: iconst_2
15: invokevirtual #8 // Method setAge:(I)V
18: return
LineNumberTable:
line 34: 0
line 35: 5
line 36: 13
line 37: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 this Lcom/example/fragmentadaptertest/testjava/TestA;
0 19 1 name Ljava/lang/String;
13 6 2 testA Lcom/example/fragmentadaptertest/testjava/TestA;
}
SourceFile: "TestA.java"
从这个字节码文件中我有了新的认识。
这个字节码文件已经按照java虚拟机规范中的Class文件格式把信息存好了,例如:
字段表、方法表、属性表、运行时常量池
看过java虚拟机规范后就知道这个结构为啥是这样的,例如
方法的descriptor方法描述符:是描述方法的参数和返回值的
flags是访问权限这些public、static、final等
方法的attribute 属性中的code[]是存放方法的内容字节码的,LineNumberTable是存放上面的字节码对应的java代码的多少行,调试的时候用的等
(1).运行时常量池:
有一篇文章有助于理解常量池:
[java]JVM之运行时常量池里到底有什么 - 简书1. 概念 首先我们来复习一下java内存模型,java运行时数据区大概分为五块,分别是 方法区 虚拟机栈 本地方法栈 堆 程序计数器 而运行时常量池是方法区的一部分,文字解...https://www.jianshu.com/p/614e2b6a0f22常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符 号引用则属于编译原理方面的概念,包括了下面三类常量:
类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符(Descriptor)
(2).访问标志 access_flags
(3).类索引、父类索引、接口索引集合
(4) 字段表集合
(5).方法表集合
(6) 属性表集合
2.加载在JVM中如何保存
这就要提到类的加载:
加载、验证、准备、解析、初始化、使用、卸载
(1).加载
(2) 验证
(3) 准备
(4).解析
(5) 初始化
二、JVM具体是如何执行代码的
1.执行引擎
更多推荐



所有评论(0)