Java字节码是Java源代码经过编译器编译生成的二进制格式,它是一种面向栈的指令集架构。Java字节码由一系列指令组成,用于执行各种操作,如加载和存储数据、进行算术和逻辑运算、控制流程等。
字节码是由Java虚拟机(JVM)解释和执行的。JVM将字节码加载到内存中,并根据指令逐条执行。这种中间语言的设计使得Java可以实现平台无关性,因为不同的操作系统和硬件只需提供对应的JVM实现即可执行Java字节码。
Java源码
public class SynchronizedDemo {
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
synchronized (synchronizedDemo) {
System.out.println("synchronized demo ...");
}
}
}
编译后的class文件
package com.fei.multiThread.threadPool;
public class SynchronizedDemo {
public SynchronizedDemo() {
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
synchronized(synchronizedDemo) {
System.out.println("synchronized demo ...");
}
}
}
查看字节码,在IDEA中点击编译后的class,View ---> Show Bytecode
// class version 52.0 (52)
// access flags 0x21 表示该类、字段或方法具有公共访问级别,并且在调用父类方法时使用特殊调用指令。
public class com/fei/multiThread/threadPool/SynchronizedDemo {
// compiled from: SynchronizedDemo.java
// access flags 0x1 表示该类、字段或方法具有公共访问级别。
public <init>()V 表示一个公共的构造方法,没有参数,并且没有返回值。
L0 L0 是一个标签,用于标识字节码中的一个位置。在这段字节码中,L0 标签出现在构造方法 <init>()V 的开始处。
LINENUMBER 8 L0 表示源代码的行号为 8,对应于 L0 标签所在的位置。
ALOAD 0 将当前对象引用加载到操作数栈上。
INVOKESPECIAL java/lang/Object.<init> ()V 调用了父类 java/lang/Object 的构造方法,以初始化对象。
RETURN 表示从构造方法中返回。
L1 在这段字节码中,L1 标签出现在构造方法 <init>()V 的结束处。
LOCALVARIABLE this Lcom/fei/multiThread/threadPool/SynchronizedDemo; L0 L1 0 表示在方法范围内定义了一个局部变量 this,它的类型是 Lcom/fei/multiThread/threadPool/SynchronizedDemo;(即当前类的类型),在代码范围从 L0 到 L1 处有效,且该局部变量的槽位索引为 0。
MAXSTACK = 1 表示在方法执行时,操作数栈的最大深度为 1。
MAXLOCALS = 1 表示在方法执行时,局部变量表的最大槽位索引为 1。
// access flags 0x9 表示该字段或方法具有公共访问级别,是静态的,并且是不可变的。
public static main([Ljava/lang/String;)V 表示一个公共的静态方法 main,该方法接受一个 String 数组作为参数,并且没有返回值。
// parameter args 表示方法的参数名为 args,它是一个 String 数组。
TRYCATCHBLOCK L0 L1 L2 null 表示在 L0 到 L1 的代码范围内可能会抛出异常,并且在发生异常时,将跳转到 L2 处进行异常处理。 null:表示异常类型为任意异常。
TRYCATCHBLOCK L2 L3 L2 null 表示在 L2 到 L3 的代码范围内可能会抛出异常,并且在发生异常时,将跳转到 L2 处进行异常处理。
L4 L4 标签出现在 main 方法中的一系列指令的开始处。
LINENUMBER 10 L4 表示源代码的行号为 10,对应于 L4 标签所在的位置。
NEW com/fei/multiThread/threadPool/SynchronizedDemo 创建了一个新的 com/fei/multiThread/threadPool/SynchronizedDemo 类型的对象。
DUP 复制了对象引用,将其放入操作数栈的顶部。
INVOKESPECIAL com/fei/multiThread/threadPool/SynchronizedDemo.<init> ()V 调用了 com/fei/multiThread/threadPool/SynchronizedDemo 类的构造方法 <init>(),用于初始化对象。
ASTORE 1 将操作数栈顶部的对象引用存储到局部变量表的索引 1 处。
L5 L5 标签出现在字节码中的第 5 行,它表示在 ALOAD 1 指令之后的位置。
LINENUMBER 11 L5 表示字节码中的 L5 标签所在位置对应于源代码的第 11 行。
ALOAD 1 将局部变量表索引 1 处的对象引用加载到操作数栈上。
DUP 复制操作数栈顶部的对象引用,并将其放入操作数栈的顶部。
ASTORE 2 将操作数栈顶部的对象引用存储到局部变量表索引 2 处。
MONITORENTER 进入对象的监视器(获取对象锁)。
L0 L0 标签出现在一系列指令的开始处。
LINENUMBER 12 L0 表示源代码的行号为 12,对应于 L0 标签所在的位置。
GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 获取了 java.lang.System 类中的静态字段 out,它是一个 java.io.PrintStream 类型的对象。
LDC "synchronized demo ..." 将字符串常量 "synchronized demo ..." 加载到操作数栈上。
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 调用了 PrintStream 类的 println 方法,将字符串常量打印到控制台。
L6
LINENUMBER 13 L6 表示源代码的行号为 13,对应于 L6 标签所在的位置。
ALOAD 2 将局部变量表索引 2 处的对象引用加载到操作数栈上。
MONITOREXIT 退出对象的监视器(释放对象锁)。
L1
GOTO L7 是一条无条件跳转指令,它会将程序的控制流程直接跳转到标签 L7 所指向的位置。
L2
FRAME FULL 表示字节码中的栈帧信息。 [[Ljava/lang/String; com/fei/multiThread/threadPool/SynchronizedDemo java/lang/Object] [java/lang/Throwable]
表示当前方法的局部变量表中的数据类型。 表示当前方法可能抛出的异常类型。
ASTORE 3 将操作数栈顶部的对象引用存储到局部变量表索引 3 处。
ALOAD 2 将局部变量表索引 2 处的对象引用加载到操作数栈上。
MONITOREXIT 退出对象的监视器(释放对象锁)。 以确保即使在发生异常时,锁也能被正确地释放。
L3
ALOAD 3 将局部变量表索引 3 处的对象引用加载到操作数栈上。
ATHROW 抛出当前操作数栈顶部的异常对象。
L7
LINENUMBER 14 L7 表示源代码的行号为 14,对应于 L7 标签所在的位置。
FRAME CHOP 1 指示当前方法调用的栈帧中,删除一个局部变量。这意味着局部变量表中的一个槽位被释放,可以用于存储其他数据。
RETURN 表示从当前方法返回。
L8
LOCALVARIABLE args [Ljava/lang/String; L4 L8 0 表示方法的第一个局部变量名为 args,类型为 java.lang.String[],它在字节码中的范围是从标签 L4 到 L8,并且它在局部变量表中的索引为 0。
LOCALVARIABLE synchronizedDemo Lcom/fei/multiThread/threadPool/SynchronizedDemo; L5 L8 1 表示方法的第二个局部变量名为 synchronizedDemo,类型为 com.fei.multiThread.threadPool.SynchronizedDemo,它在字节码中的范围是从标签 L5 到 L8,并且它在局部变量表中的索引为 1。
MAXSTACK = 2 表示该方法的操作数栈的最大深度为 2。
MAXLOCALS = 4 表示该方法的局部变量表的最大容量为 4。
}
在Java字节码中,L0、L1、L2等标签(Label)通常用于表示代码块或分支的目标位置。这些标签用于标记字节码中的特定位置,以便在控制流程跳转时进行引用。
具体而言,L0、L1、L2等标签可以表示以下几种情况:
-
循环结构:在字节码中,循环结构通常使用
goto
指令或条件分支指令(如ifeq
、ifne
等)来实现。L0、L1、L2等标签可以用于标记循环的起始位置和结束位置,以便在控制流程跳转时进行引用。 -
分支结构:在字节码中,分支结构通常使用条件分支指令(如
ifeq
、ifne
等)或switch
指令来实现。L0、L1、L2等标签可以用于标记不同分支的目标位置,以便在控制流程跳转时进行引用。 -
异常处理:在字节码中,异常处理通常使用
try-catch-finally
块来实现。L0、L1、L2等标签可以用于标记try
块、catch
块和finally
块的起始位置和结束位置,以便在发生异常时进行跳转和处理。
需要注意的是,L0、L1、L2等标签只是字节码中的符号,它们的具体名称和用途可能会根据编译器、优化器或其他工具的不同而有所变化。在字节码转换为可执行代码时,这些标签通常会被转换为具体的跳转指令或异常处理指令。因此,在阅读和理解字节码时,我们可以将L0、L1、L2等标签视为代码块或分支的目标位置的占位符。