jvm指令手册_深入JVM(三)Class文件结构实例分析

深入JVM(一)JVM指令手册

深入JVM(二)JVM概述

一. 环境准备

Class文件结构环境准备主要使用到了javap,ASM字节码框架和jclasslib工具

1. javap命令

有如下的java源文件

public interface DemoInterface {

void test(int paramInt);
}


public class DemoClass implements DemoInterface {

private String info = "hello world";

public void test(int paramInt){
paramInt++;
}
}

编译过后,可以使用 javap 来查阅 DemoClass 的字节码

javac DemoClass.java
javap -v -p DemoClass.class

第一个选项是 -p。默认情况下 javap 会打印所有非私有的字段和方法,当加了 -p 选项后,它还将打印私有的字段和方法
第二个选项是 -v。它尽可能地打印所有信息。

Classfile /Users/Randy/Developer/1Projects/6Self/demo/src/main/java/DemoClass.class
Last modified Sep 6, 2020; size 668 bytes
MD5 checksum 8570320ab11f209c38eb847caac27357
Compiled from "DemoClass.java"

public class DemoClass implements DemoInterface
....
}
SourceFile: "DemoClass.java"

2. jclasslib bytecode viewer

jclasslib bytecode viewer是idea中的一个插件,本质上javap -v -p按照图形化的方式展示出来

f77c4b4bf5d79caa10b408a09136bb6f.png

二.Class文件分析

以上javap命令的输出内容本质上就是把class文件内容打印出来,其结构是按照 深入JVM(三)Class文件结构 中的类文件结构图展示的

92030777c1f80f231ad432f299e69c7f.png

代码输出的完整内容如下:

Classfile /Users/Randy/Developer/1Projects/6Self/demo/target/classes/com/randy/DemoClass.class
Last modified Sep 6, 2020; size 479 bytes
MD5 checksum 5c9ff7c479dcb22e279b9726426d0131
Compiled from "DemoClass.java"
public class com.randy.DemoClass implements com.randy.DemoInterface
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#22 // java/lang/Object."":()V
#2 = String #23 // hello world
#3 = Fieldref #4.#24 // com/randy/DemoClass.info:Ljava/lang/String;
#4 = Class #25 // com/randy/DemoClass
#5 = Class #26 // java/lang/Object
#6 = Class #27 // com/randy/DemoInterface
#7 = Utf8 info
#8 = Utf8 Ljava/lang/String;
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/randy/DemoClass;
#16 = Utf8 test
#17 = Utf8 (I)V
#18 = Utf8 paramInt
#19 = Utf8 I
#20 = Utf8 SourceFile
#21 = Utf8 DemoClass.java
#22 = NameAndType #9:#10 // "":()V
#23 = Utf8 hello world
#24 = NameAndType #7:#8 // info:Ljava/lang/String;
#25 = Utf8 com/randy/DemoClass
#26 = Utf8 java/lang/Object
#27 = Utf8 com/randy/DemoInterface
{
private java.lang.String info;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE

public com.randy.DemoClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: ldc #2 // String hello world
7: putfield #3 // Field info:Ljava/lang/String;
10: return
LineNumberTable:
line 8: 0
line 10: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/randy/DemoClass;

public void test(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: iinc 1, 1
3: return
LineNumberTable:
line 13: 0
line 14: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lcom/randy/DemoClass;
0 4 1 paramInt I
}
SourceFile: "DemoClass.java"

总体上可以分为四大区域:

2.1. 基本信息区域:

  • 魔数:前4个字节(CA FE BA BE)用来确定这个文件是否是一个可以被JVM读取的class文件,但是在上面的输出中没有展示。而通过文本工具可以查看如下:

c80b05c426ac57421edd5269ae80fd43.png

  • 主副版本号:class 文件的版本号指的是编译生成该 class 文件时所用的 JRE 版本。由较新的 JRE 版本中的 javac 编译而成的 class 文件,不能在旧版本的 JRE 上跑,否则,会出现UnsupportedClassVersionError异常。52对应的是Java 8

  • 访问标志:flags: ACC_PUBLIC, ACC_SUPER 标志这个类的访问权限

0e7551318035ffb9f6b6c02849f2cd94.png

  • 类索引:常量池中#7 = Class #29 // DemoClass

  • 父索引:#8 = Class #30 // java/lang/Object,java中出了Object意外的所有类都存在父类

  • 实现接口:#9 = Class #31 // DemoInterface

  • 接口计数器:1

  • 字段计数器:0

  • 方法计数器:2 一个是方法test,另外一个是默认的构造方法

2.22. 常量池:

常量池是一个类似于数组的数据结构,中的每一项都有一个对应的索引(如 #1),并且可能引用其他的常量池项。
常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”

Constant pool:
#1 = Methodref #5.#22 // java/lang/Object."":()V
#2 = String #23 // hello world
#3 = Fieldref #4.#24 // com/randy/DemoClass.info:Ljava/lang/String;
#4 = Class #25 // com/randy/DemoClass
#5 = Class #26 // java/lang/Object
#6 = Class #27 // com/randy/DemoInterface
#7 = Utf8 info
#8 = Utf8 Ljava/lang/String;
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/randy/DemoClass;
#16 = Utf8 test
#17 = Utf8 (I)V
#18 = Utf8 paramInt
#19 = Utf8 I
#20 = Utf8 SourceFile
#21 = Utf8 DemoClass.java
#22 = NameAndType #9:#10 // "":()V
#23 = Utf8 hello world
#24 = NameAndType #7:#8 // info:Ljava/lang/String;
#25 = Utf8 com/randy/DemoClass
#26 = Utf8 java/lang/Object
#27 = Utf8 com/randy/DemoInterface

常量池主要用于存放各种字面量符号引用,可以理解为class文件中的资源仓库。
字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:

  1. 类和接口的全限定名,也就是Ljava/lang/String;这样,将类名中原来的"."替换为"/"得到的,主要用于在运行时解析得到类的直接引用

#7 = Class              #37            // com/randy/DemoClass
  1. 字段的名称和描述符,字段也就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量

#2 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #32 // paramInt should be great than 0
  1. 方法的名称和描述符,方法的描述类似于JNI动态注册时的“方法签名”,也就是参数类型+返回值类型

  #18 = Utf8               ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 test
#22 = Utf8 (I)V

java代码在进行javac编译的时候并不会解析为内存中的最终布局,必须要经过运行期转换才能得到最终的内存入口地址。

2.3. 字段区域:

字段包括类级变量以及实例变量,用于描述接口或类中声明的变量

#3 = Fieldref           #4.#24         // com/randy/DemoClass.info:Ljava/lang/String;
#4 = Class #25 // com/randy/DemoClass
#7 = Utf8 info
#8 = Utf8 Ljava/lang/String;
#24 = NameAndType #7:#8 // info:Ljava/lang/String;

2.4. 方法区域:

用来列举该类中的各个方法,包括:方法描述符,访问权限和Code代码区域

ea7633d4fe09ffed1c116f9af8fe8e99.png

从截图可以看出这个类有两个方法,如构造方法:

#1 = Methodref          #5.#22         // java/lang/Object."":()V
#5 = Class #26 // java/lang/Object
#22 = NameAndType #9:#10 // "":()V

public com.randy.DemoClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: ldc #2 // String hello world
7: putfield #3 // Field info:Ljava/lang/String;
10: return
LineNumberTable:
line 8: 0
line 10: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/randy/DemoClass;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值