为什么java具有跨平台能力
java能够实现“一次编译,到处运行”的原因
java虚拟机支持很多种语言,这些语言经过编译后可以生成能够被jvm解析并运行的字节码文件。
class文件数据结构
class文件只有两种数据结构:无符号数和 表
无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者字符串(UTF-8 编码)。
表:表是由多个无符号数或者其他表作为数据项构成的复合数据类型,class文件中所有的表都以“_info”结尾。其实,整个 Class 文件本质上就是一张表。
class文件结构
JVM解析某个class文件,就是根据上图结构去解析class文件,加载class文件到内存,并在内存中分配相应空间
某种结构和占用空间如图:
魔数
在 class 文件开头的四个字节是 class 文件的魔数,它是一个固定的值--0XCAFEBABE。
如果开头四个字节不是 0XCAFEBABE, 那么就说明它不是 class 文件, 不能被 JVM 识别或加载。
版本号
紧跟在魔数后面的两个字节代表当前class文件的版本号。前两个字节0000代表次版本号(minor_version),后两个字节0034是主版本号(major_version),对应的十进进制值为 52,也就是说当前 class 文件的主版本号为 52,次版本号为 0。所以综合版本号是 52.0,也就是jdk1.8.0
常量池
在常量池中保存了类的各种相关信息,比如类的名称、父类的名称、类中的方法名、参数名称、参数类型等,
常量池中的每一项都是一个表,其项目类型共有 14 种,如下表所示:
例如:
CONSTANT_Methodref_info{
u1 tag=10; 说明是CONSTANT_Methodref_info表
u2 class_index;指向此方法的所属类
u2 name_type_index;指向此方法的名称和类型
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;指向某字段或方法的名称字符串
u2 type_index;指向某字段或方法的类型字符串
0006:十进制 6,表示指向常量池中的第 6 个常量。
0015:十进制 21,表示指向常量池中的第 21 个常量。
最终值
访问标志
类索引、父类索引、接口索引计数器
字段表
字段表的主要功能是用来描述类或者接口中声明的变量
字段计数器:有几个字段
字段结构有:访问标志,指向NameAndType的索引
方法表
方法计数器:有几个方法
方法结构有:方法的访问标志,指向方法名,指向方法类型
属性表
JVM 中预定义了很多属性表,这里重点讲一下 Code 属性表。
Code 属性表中,最主要的就是一些列的字节码。
实例
Test类:
package com.example.study.reflect;
import java.io.Serializable;
public class Test implements Cloneable, Serializable {
private int num = 1;
public int add() {
int i=2;
int j=4;
int result =i+j;
return result+10;
}
}
AndroidStudio自定义External Tools查看字节码
对应的字节码
"/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home\bin\javap" -c -verbose com.example.study.reflect.Test
Classfile /Users/yy/code/private/study_project/app/build/intermediates/javac/debug/classes/com/example/study/reflect/Test.class
Last modified 2020-4-14; size 521 bytes
MD5 checksum fea1a0449cdb4125e04f9536d0a93fdc
Compiled from "Test.java"
public class com.example.study.reflect.Test implements java.lang.Cloneable,java.io.Serializable
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#23 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#24 // com/example/study/reflect/Test.num:I
#3 = Class #25 // com/example/study/reflect/Test
#4 = Class #26 // java/lang/Object
#5 = Class #27 // java/lang/Cloneable
#6 = Class #28 // java/io/Serializable
#7 = Utf8 num
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/example/study/reflect/Test;
#16 = Utf8 add
#17 = Utf8 ()I
#18 = Utf8 i
#19 = Utf8 j
#20 = Utf8 result
#21 = Utf8 SourceFile
#22 = Utf8 Test.java
#23 = NameAndType #9:#10 // "<init>":()V
#24 = NameAndType #7:#8 // num:I
#25 = Utf8 com/example/study/reflect/Test
#26 = Utf8 java/lang/Object
#27 = Utf8 java/lang/Cloneable
#28 = Utf8 java/io/Serializable
{
public com.example.study.reflect.Test();
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: iconst_1
6: putfield #2 // Field num:I
9: return
LineNumberTable:
line 5: 0
line 6: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/example/study/reflect/Test;
public int add();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: iconst_2
1: istore_1
2: iconst_4
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: iload_3
9: bipush 10
11: iadd
12: ireturn
LineNumberTable:
line 9: 0
line 10: 2
line 11: 4
line 12: 8
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lcom/example/study/reflect/Test;
2 11 1 i I
4 9 2 j I
8 5 3 result I
}
SourceFile: "Test.java"
Process finished with exit code 0
java文件通过编译器编译成class文件,class文件通过类加载器加载到内存,jvm加载class文件到具体过程下一遍讲。
装载其中一个步骤就是JVM 会将这些 .class 文件的结构转化为 JVM 内部的运行时数据结构。这点同 JSON 解析过程有点类似。
学习于: