目录
概述
Java代码通过javac编译成字节码
JVM可以识别字节码,一处编写处处运行
JVM相当于是操作系统的中间一层抽象,屏蔽平台差异性
字节码并不是某种虚拟 CPU 的机器码,而是一种介于源码和机器码中间的一种抽象表示方法,不过字节码通过 JIT(Just in time)技术可以被进一步编译成机器码。
体验字节码
上小demo
public class Xinye {
public static void main(String[] args) {
System.out.println("xinyeshuaiqi");
}
}
进入代码目录
~/Documents/code/xinye/src/main/java/pers/wmx/springbootfreemarkerdemo(master*) »
编译
javac Xinye.java
用xxd 命令以 16 进制的方式查看这个 class 文件。
xxd Xinye.class
00000000: cafe babe 0000 0034 001d 0a00 0600 0f09 .......4........
00000010: 0010 0011 0800 120a 0013 0014 0700 1507 ................
00000020: 0016 0100 063c 696e 6974 3e01 0003 2829 .....<init>...()
00000030: 5601 0004 436f 6465 0100 0f4c 696e 654e V...Code...LineN
00000040: 756d 6265 7254 6162 6c65 0100 046d 6169 umberTable...mai
00000050: 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 n...([Ljava/lang
00000060: 2f53 7472 696e 673b 2956 0100 0a53 6f75 /String;)V...Sou
00000070: 7263 6546 696c 6501 000a 5869 6e79 652e rceFile...Xinye.
00000080: 6a61 7661 0c00 0700 0807 0017 0c00 1800 java............
00000090: 1901 000c 7869 6e79 6573 6875 6169 7169 ....xinyeshuaiqi
000000a0: 0700 1a0c 001b 001c 0100 2770 6572 732f ..........'pers/
000000b0: 776d 782f 7370 7269 6e67 626f 6f74 6672 wmx/springbootfr
000000c0: 6565 6d61 726b 6572 6465 6d6f 2f58 696e eemarkerdemo/Xin
000000d0: 7965 0100 106a 6176 612f 6c61 6e67 2f4f ye...java/lang/O
000000e0: 626a 6563 7401 0010 6a61 7661 2f6c 616e bject...java/lan
000000f0: 672f 5379 7374 656d 0100 036f 7574 0100 g/System...out..
00000100: 154c 6a61 7661 2f69 6f2f 5072 696e 7453 .Ljava/io/PrintS
00000110: 7472 6561 6d3b 0100 136a 6176 612f 696f tream;...java/io
00000120: 2f50 7269 6e74 5374 7265 616d 0100 0770 /PrintStream...p
00000130: 7269 6e74 6c6e 0100 1528 4c6a 6176 612f rintln...(Ljava/
00000140: 6c61 6e67 2f53 7472 696e 673b 2956 0021 lang/String;)V.!
00000150: 0005 0006 0000 0000 0002 0001 0007 0008 ................
00000160: 0001 0009 0000 001d 0001 0001 0000 0005 ................
00000170: 2ab7 0001 b100 0000 0100 0a00 0000 0600 *...............
00000180: 0100 0000 0700 0900 0b00 0c00 0100 0900 ................
00000190: 0000 2500 0200 0100 0000 09b2 0002 1203 ..%.............
000001a0: b600 04b1 0000 0001 000a 0000 000a 0002 ................
000001b0: 0000 0009 0008 000a 0001 000d 0000 0002 ................
000001c0: 000e ..
javap -c Xinye.class
对类进行反编译。
Compiled from "Xinye.java"
public class pers.wmx.springbootfreemarkerdemo.Xinye {
public pers.wmx.springbootfreemarkerdemo.Xinye();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String xinyeshuaiqi
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
invokespecial 指令调用实例初始化方法、私有方法、父类方法,#1 指的是常量池中的第一个,这里是方法引用java/lang/Object.”<init>”:()V,也即构造器函数
return,这个操作码属于 ireturn、lreturn、freturn、dreturn、areturn 和 return 操作码组中的一员,其中 i 表示 int,返回整数,同类的还有 l 表示 long,f 表示 float,d 表示 double,a 表示 对象引用。没有前缀类型字母的 return 表示返回 void
getstatic #2,getstatic 获取指定类的静态域,并将其值压入栈顶,#2 代表常量池中的第 2 个,这里表示的是java/lang/System.out:Ljava/io/PrintStream;,其实就是java.lang.System 类的静态变量 out(类型是 PrintStream)
ldc #3、,ldc 用来将常量从运行时常量池压栈到操作数栈,#3 代表常量池的第三个(字符串 Hello, World)
invokevirtual #4,invokevirutal 指令调用一个对象的实例方法,#4 表示 PrintStream.println(String) 函数引用,并把栈顶两个元素出栈
javap -v Xinye.class
Classfile /Users/xinye/Documents/code/xinye/src/main/java/pers/wmx/springbootfreemarkerdemo/Xinye.class
Last modified 2022-6-13; size 450 bytes
MD5 checksum 5961df6f642b1f8b652adf6d1ed5eda1
Compiled from "Xinye.java"
public class pers.wmx.springbootfreemarkerdemo.Xinye
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // xinyeshuaiqi
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // pers/wmx/springbootfreemarkerdemo/Xinye
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Xinye.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 xinyeshuaiqi
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 pers/wmx/springbootfreemarkerdemo/Xinye
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public pers.wmx.springbootfreemarkerdemo.Xinye();
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 7: 0
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 xinyeshuaiqi
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 9: 0
line 10: 8
}
SourceFile: "Xinye.java"
class文件结构
这一部分还是比较复杂的
class 文件由下面十个部分组成
- 魔数(Magic Number)
- 版本号(Minor&Major Version)
- 常量池(Constant Pool)
- 类访问标记(Access Flags)
- 类索引(This Class)
- 超类索引(Super Class)
- 接口表索引(Interfaces)
- 字段表(Fields)
- 方法表(Methods)
- 属性表(Attributes)
魔数
版本号
3 * (16 ^ 1) + 4 * (16 ^ 0) = 52 52是Java 8
常量池
对于 JVM 字节码来说,如果操作数非常小或者很常用的数字 0 之类的,这些操作数是内嵌到字节码中的。如果是字符串常量和较大的整数等,class 文件是把这些操作数存储在一个叫常量池(Constant Pool)的地方,当使用这些操作数时,使用的是常量池数据中的索引位置。
常量池结构
{
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
}
每个常量项都由两部分构成:表示类型的 tag 和表示内容的字节数组
cp_info {
u1 tag;
u1 info[];
}
类访问标记
常量池之后存储的是访问标记(Access flags),用来标识一个类是是不是final、abstract 等,由两个字节表示总共可以有 16 个标记位可供使用,目前只使用了其中的 8 个。
转载请注明:汪明鑫的个人博客 » Java 字节码初体验
说点什么
您将是第一位评论人!