JVM:类加载


在这里插入图片描述

类文件结构

一个简单的 HelloWorld.java

// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

运行后使用WinHex将其字节码文件HelloWorld.class打开:
在这里插入图片描述
上面打开的就是我们编译后的HelloWorld.class二进制字节码文件,我么要做的就是分析并能够看懂上面的内容。

事实上,现在有很多反编译软件,比如在IDEA的Terminal终端就可以将HelloWorld.class进行反编译,将上面看不懂的二进制数字反编译为看得懂的程序代码。

下面就是将上面的二进制字节码反编译后的结果:

F:\JVM虚拟机\JVM_黑马\JVM_01\out\production\JVM_01\cn\itcast\jvm\t5>javap -v HelloWorld.class
Classfile /F:/JVM虚拟机/JVM_黑马/JVM_01/out/production/JVM_01/cn/itcast/jvm/t5/HelloWorld.class
  Last modified 2020-3-25; size 567 bytes
  MD5 checksum 8efebdac91aa496515fa1c161184e354
  Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // hello world
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // cn/itcast/jvm/t5/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcn/itcast/jvm/t5/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               cn/itcast/jvm/t5/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public cn.itcast.jvm.t5.HelloWorld();
    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 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/itcast/jvm/t5/HelloWorld;

  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: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

我们要做的就是直接通过字节码文件解读成反编译后的结果(不通过反编译工具)。
在下面的所有分析过程,都可以通过借助反编译帮助我们理解。

根据JVM规范,类文件结构如下:

ClassFile{
	u4 magic;
	u2 minor_version;
	u2 major_version;
	u2 constant_pool_count;
	cp_info constant_pool[constant_pool_count-1];
	u2 access_flags;
	u2 this_class;
	u2 super_class;
	u2 interfaces_count;
	u2 interfaces[interfaces_count];
	u2 fields_count;
	field_info fields[fields_count];
	u2 methods_count;
	method_info methods[methods_count];
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}
1. 魔数

0~3 字节,表示它是否是【class】类型的文件
在这里插入图片描述

2. 版本

4~7 字节,表示类的版本 00 34(52) 表示是 Java 8
在这里插入图片描述

3. 常量池

常量结构表:
在这里插入图片描述
8~9 字节,表示常量池长度,00 22 (34) 表示常量池有 #1~#33项,注意 #0 项不计入,也没有值
在这里插入图片描述
#1项 0A(tag=10)表示一个 Method 信息,00 06 和 00 14(20) 表示它引用了常量池中 #6 和 #20 项来获得这个方法的【所属类】和【方法名】
在这里插入图片描述
#2项 09(tag=9) 表示一个 Field 信息,00 15(21)和 00 16(22) 表示它引用了常量池中 #21和 # 22 项来获得这个成员变量的【所属类】和【成员变量名】
在这里插入图片描述
#3项 08(tag=8) 表示一个字符串常量名称,00 17(23)表示它引用了常量池中 #23项
在这里插入图片描述
#4项 0A(tag=10)表示一个 Method 信息,00 18(24) 和 00 19(25) 表示它引用了常量池中 #24 和 #25项来获得这个方法的【所属类】和【方法名】
在这里插入图片描述
#5项 07(tag=7) 表示一个 Class 信息,00 1A(26) 表示它引用了常量池中 #26 项
在这里插入图片描述
#6项 07 (tag=7)表示一个 Class 信息,00 1B(27) 表示它引用了常量池中 #27 项
在这里插入图片描述
#7项 01(tag=1) 表示一个 utf8 串,00 06 表示长度,3c 69 6e 69 74 3e 是【 <init> 】
在这里插入图片描述
#8项 01 表示一个 utf8 串,00 03 表示长度,28 29 56 是【()V】其实就是表示无参、无返回值
在这里插入图片描述
#9项 01 表示一个 utf8 串,00 04 表示长度,43 6f 64 65 是【Code】
在这里插入图片描述
#10项 01 表示一个 utf8 串,00 0f(15) 表示长度,4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65是【LineNumberTable】
在这里插入图片描述
#11项 01 表示一个 utf8 串,00 12(18) 表示长度,4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 6162 6c 65是【LocalVariableTable】
在这里插入图片描述
#12项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【this】
在这里插入图片描述
#13项 01 表示一个 utf8 串,00 1d(29) 表示长度,是【Lcn/itcast/jvm/t5/HelloWorld;】
在这里插入图片描述
#14项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【main】
在这里插入图片描述
#15项 01 表示一个 utf8 串,00 16(22) 表示长度,是【([Ljava/lang/String;)V】其实就是参数为字符串数组,无返回值
在这里插入图片描述
#16项 01 表示一个 utf8 串,00 04 表示长度,是【args】
在这里插入图片描述
#17项 01 表示一个 utf8 串,00 13(19) 表示长度,是【[Ljava/lang/String;】
在这里插入图片描述
#18项 01 表示一个 utf8 串,00 0a(10) 表示长度,是【SourceFile】
在这里插入图片描述
#19项 01 表示一个 utf8 串,00 0f(15) 表示长度,是【HelloWorld.java】
在这里插入图片描述
#20项 0C(tag=12) 表示一个 【名+类型】,00 07 00 08 引用了常量池中 #7 #8 两项
在这里插入图片描述
#21项 07(tag=12) 表示一个 Class 信息,00 1C(28) 引用了常量池中 #28 项
在这里插入图片描述
#22项 0C(tag=12)表示一个 【名+类型】,00 1D(29) 00 1E(30)引用了常量池中 #29 #30两项
在这里插入图片描述
#23项 01 表示一个 utf8 串,00 0B(11) 表示长度,是【hello world】
在这里插入图片描述
#24项 07 表示一个 Class 信息,00 1F(31) 引用了常量池中 #31 项
在这里插入图片描述
#25项 0c 表示一个 【名+类型】,00 20(32) 00 21(33)引用了常量池中 #32 #33 两项
在这里插入图片描述
#26项 01 表示一个 utf8 串,00 1b(27) 表示长度,是【cn/itcast/jvm/t5/HelloWorld】
在这里插入图片描述
#27项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/Object】
在这里插入图片描述
#28项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/System】
在这里插入图片描述
#29项 01 表示一个 utf8 串,00 03 表示长度,是【out】
在这里插入图片描述
#30项 01 表示一个 utf8 串,00 15(21) 表示长度,是【Ljava/io/PrintStream;】
在这里插入图片描述
#31项 01 表示一个 utf8 串,00 13(19) 表示长度,是【java/io/PrintStream】
在这里插入图片描述
#32项 01 表示一个 utf8 串,00 07 表示长度,是【println】
在这里插入图片描述
#33项 01 表示一个 utf8 串,00 15(21) 表示长度,是【(Ljava/lang/String;)V】
在这里插入图片描述

4. 类的访问标识与继承信息

根据JVM类文件规范可以得到下面要分析的:前面u2代表每个信息占用的字节数
在这里插入图片描述
在这里插入图片描述类索引用于确定这个类的全限定类名
父类索引用于确定这个类的父类的全限定类名
接口索引集合用于描述这个类实现了哪些接口
因为java实现单继承,多实现,因此父类只有一个,接口可以有多个。

21 表示该 class 是一个类,公共的(0021=0001+0020=public+super)
在这里插入图片描述
05 表示根据常量池中 #5 找到本类全限定名
在这里插入图片描述

06 表示根据常量池中 #6 找到父类全限定名
在这里插入图片描述
表示接口的数量,本类为 0(HelloWorld.java没有实现接口)
在这里插入图片描述

5. Field 信息

下面哟啊分析的就是类的成员变量的信息:
在这里插入图片描述
表示成员变量数量,本类为 0
在这里插入图片描述
在描述这些成员变量时,可能会用到字节码里面的类型表示:字节码为了更加紧凑压缩了大小用了更简洁的字符来表示这些数据类型。

标志符含义
B基本数据类型byte
C基本数据类型char
D基本数据类型double
F基本数据类型float
I基本数据类型int
J基本数据类型long
S基本数据类型short
Z基本数据类型boolean
V基本数据类型void
L ClassName;对象类型,如Ljava/lang/Object
[一维数组
6. Method信息

在这里插入图片描述
表示方法数量,本类为 2(构造方法和main方法)
在这里插入图片描述
一个方法由访问修饰符,名称,参数描述,方法属性数量,方法属性组成:

MethodFile{
	u2 access_flags;
	u2 name_index;
	u2 descriptor_index;
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}
6.1 method-init(构造方法)

方法的访问标识表:
在这里插入图片描述00 01代表访问修饰符(本类中是方法为 public)
在这里插入图片描述

引用了常量池 #07 项作为方法名称
在这里插入图片描述
引用了常量池 #08 项作为方法参数描述
在这里插入图片描述
代表方法属性数量,本方法是 1
在这里插入图片描述
目前构造方法的访问修饰符,名称,参数描述,方法属性数量都已分析完:
在这里插入图片描述
对于属性信息(比如方法体对应的就是Code属性),开始一定通过一个u2字节的索引确定属性

u2  attribute_name_index

00 09 表示引用了常量池 #09 项,发现是【Code】属性
在这里插入图片描述
下面进入【Code】属性表分析属性:

CodeAttribute{
	u2  attribute_name_index  //这个已分析
	u4  attribute_length 
	u2  max_stack
	u2  max_locals
	u4  code_length
	code_length  code 
	u2  exception_table_length
	exception_info  exception_count
	u2  attributes_count
	attribute_info  attributes
}

00 00 00 2f 表示此属性的长度是 47
在这里插入图片描述
00 01 表示【操作数栈】最大深度(max_stack)
在这里插入图片描述
00 01 表示【局部变量表】最大槽(slot)数(max_locals)
在这里插入图片描述
00 00 00 05表示字节码长度
在这里插入图片描述
2a b7 00 01 b1 是字节码指令
在这里插入图片描述
00 代表方法抛出的异常为0
在这里插入图片描述
在这里插入图片描述
00 02 表示方法细节属性数量(即Code属性中的属性,同也是方法的属性),本例是 2
在这里插入图片描述
同样对于属性信息先通过一个u2字节的索引确定属性:

u2  attribute_name_index

00 0a 表示引用了常量池 #10 项,发现是【LineNumberTable】属性
在这里插入图片描述
因此下面就进入【LineNumberTable】属性
这个属性是为了价格字节码的行号和java源码的行号进行对应

LineNumberTableAttribute{
	u2  attribute_name_index  //这个已分析
	u4  attribute_length
	u2  line_number_table_length
	line_numer_info  line_number_table
}

00 00 00 06 表示此属性的总长度,本例是 6
在这里插入图片描述
00 01 表示【LineNumberTable】长度
在这里插入图片描述
00 00 表示【字节码】行号 00 04 表示【java 源码】行号
在这里插入图片描述
至此Code属性中的第一个属性信息已经分析完毕:
在这里插入图片描述
下面分析Code属性中的第二个属性:

00 0b 表示引用了常量池 #11 项,发现是【LocalVariableTable】属性
在这里插入图片描述
进入【LocalVariableTable】属性表:

LocalVariableTableAttribute{
	u2  attribute_name_index  //这个已分析
	u4  attribute_length  
	u2  local_variable_table_length
	local_variable_info  local_variable_table
}

00 00 00 0c 表示此属性的总长度,本例是 12
在这里插入图片描述
在这里插入图片描述
local_variable_info项目代表了一个栈帧与源码中的局部变量的关联
在这里插入图片描述
00 00 表示局部变量生命周期开始,相对于字节码的偏移量
在这里插入图片描述
00 05 表示局部变量覆盖的范围长度
在这里插入图片描述
00 0c 表示局部变量名称,本例引用了常量池 #12 项,是【this】
在这里插入图片描述
00 0d 表示局部变量的类型,本例引用了常量池 #13 项,是【Lcn/itcast/jvm/t5/HelloWorld;】
在这里插入图片描述
00 00 表示局部变量占有的槽位(slot)编号,本例是 0
在这里插入图片描述
至此整个构造方法分完毕。

6.2 method-main(主函数)

按照分析构造方法的思路去分析即可:
在这里插入图片描述

7. 附加属性

00 01 表示附加属性数量
在这里插入图片描述
00 12 表示引用了常量池 #18 项,即【SourceFile】
在这里插入图片描述
00 00 00 02 表示此属性的长度
在这里插入图片描述
00 13 表示引用了常量池 #19 项,即【HelloWorld.java】
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值