JVM基础 - 类字节码详解
源代码通过编译器编译成字节码,再通过类加载子系统进行加载到JVM中运行
JVM是Java虚拟机,Java是高级语言,只有人类才能理解其逻辑,因此计算机不能直接识别Java源代码,所以Java代码需要通过编译器编译成class文件,最后通过jvm对字节码文件进行加载运行。
Java字节码文件
class文件本质上是一个以8为字节为基础单位的二进制文件。
jvm根据其特定的规则解析该二进制文件,从而得到相关信息。
Class文件采用一种伪结构来存储数据,它有两种类型:无符号数和表
Class文件的结构属性
在理解之前先整天看下java字节码文件包含了哪些类型的数据:
从一个例子开始
下面以一个简单的例子来逐步讲解字节码
// Main.java
public class Main{
private int m;
public int inc(){
return m + 1;
}
}
通过一下命令,可以在当前所在路径下生成一个Main.class文件。
javac Main.java
以文本的形式打开生成的class文件,内容如下:
0000000: cafe babe 0000 0034 0013 0a00 0400 0f09 .......4........
0000010: 0003 0010 0700 1107 0012 0100 016d 0100 .............m..
0000020: 0149 0100 063c 696e 6974 3e01 0003 2829 .I...<init>...()
0000030: 5601 0004 436f 6465 0100 0f4c 696e 654e V...Code...LineN
0000040: 756d 6265 7254 6162 6c65 0100 0369 6e63 umberTable...inc
0000050: 0100 0328 2949 0100 0a53 6f75 7263 6546 ...()I...SourceF
0000060: 696c 6501 0009 4d61 696e 2e6a 6176 610c ile...Main.java.
0000070: 0007 0008 0c00 0500 0601 0004 4d61 696e ............Main
0000080: 0100 106a 6176 612f 6c61 6e67 2f4f 626a ...java/lang/Obj
0000090: 6563 7400 2100 0300 0400 0000 0100 0200 ect.!...........
00000a0: 0500 0600 0000 0200 0100 0700 0800 0100 ................
00000b0: 0900 0000 1d00 0100 0100 0000 052a b700 .............*..
00000c0: 01b1 0000 0001 000a 0000 0006 0001 0000 ................
00000d0: 0001 0001 000b 000c 0001 0009 0000 001f ................
00000e0: 0002 0001 0000 0007 2ab4 0002 0460 ac00 ........*....`..
00000f0: 0000 0100 0a00 0000 0600 0100 0000 0600 ................
0000100: 0100 0d00 0000 0200 0e0a ..........
- 文件开头的4个字节("cafe babe")称之为
魔术
,唯有"cafe babe"开头的class文件才能被虚拟机接收,这4个字节就是字节码文件的声部识别。 - 0000似乎编译器jdk版本的此版本号0,0034转换为十进制是52,是主版本号,java版本号从45开始,除1.0和1.1都是使用45.x外,以后每升一个大版本,版本号加一。也就是说,编译生成该class文件的jdk版本为1.8.0
通过java -version命令验证,可得结果。
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
反编译字节码文件
使用到java内置的一个反编译工具javap可以反编译字节码文件,用法:javap <options> <classes>
其中<options>
选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
输入命令javap -verbose -p Main.class
查看输出内容:
Classfile /C:/Users/THF/Desktop/Main.class
Last modified 2023-2-14; size 265 bytes
MD5 checksum 1f1569bd4f1e84377ce2a419c1ec032d
Compiled from "Main.java"
public class Main
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#16 // Main.m:I
#3 = Class #17 // Main
#4 = Class #18 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 inc
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 Main.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = NameAndType #5:#6 // m:I
#17 = Utf8 Main
#18 = Utf8 java/lang/Object
{
private int m;
descriptor: I
flags: ACC_PRIVATE
public Main();
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 1: 0
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 6: 0
}
SourceFile: "Main.java"