首先了解一下理论知识:
字节码:
Class文件是8位字节流,按字节对齐。之所以称为字节码,是因为每条指令都只占据一个字节,所有的操作码和操作数都是按字节对齐的。如:0x03表示iconst_0
Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是用于确认该文件是否是能被JVM接受的Class文件。魔数值为:0xCAFEBABE。
紧接着魔数的4个字节是Class文件的版本号:第5和第6字节是次版本号(Minor Version),第7和第8字节是主版本号(Major Version)。Java的版本号从45开始的,JDK6的版本号是50。
javap –verbose class文件,查看字节码内容
全限定名:把类全名中的“.”替换成“/”最后加入一个“;”表示结束。如com/test/TestClass;
描述符:基本类型及void用大写字符表示,对象类型用字符L加对象的全限定名表示。
标识字符 |
含义 |
B |
基本类型byte |
C |
char |
D |
double |
F |
float |
I |
int |
J |
long |
S |
short |
Z |
boolean |
V |
void |
L |
对象类型,如Ljava/lang/Object; |
对于数组类型,每一维度将使用一个前置的“[”字符来描述,如定义一个“java.lang.String[][]”类型的二维数组,将被记录为:“[[Ljava/lang/String;”,一个整型数组“int[]”将被记录为“[I”.
用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“( )”之内,如方法void inc()的描述符为“( )V”,方法java.lang.String toString() 的描述符合为“( )Ljava/lang/String;”,方法int indexOf(char[] source, int sourceOffset,int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述符为“([CII[CIII)I”
类构造器“<clinit>”方法,实例构造器“<init>”
JVM中最基本的数据单元是字,字长必须足够大,至少一个字长足以持有byte、short、int等的值,2个字长足以持有long、double的值,字长可以选择32位或者64位。
字长是CPU的主要技术指标之一,指的是CPU一次能并行处理的二进制位数,字长总是8的整数倍,通常PC机的字长为16位(早期),32位,64位。
栈帧的2个部分:局部变量区和操作数栈,是按字来定义的。当把值放入局部变量区或者操作数栈时,它将占有1个或2个字单元。
每启动一个新线程,JVM都为它分配一个Java栈,Java栈以帧为单位保存线程的运行状态,JVM对Java栈执行2种操作:以帧为单位的压栈和出栈。
每当线程调用一个Java方法时,JVM会在线程的Java栈中压入一个新帧,而这个新帧也成了当前帧,当执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等数据。
Java方法有2种方式完成,一种通过return返回,一种通过抛出异常中止,不管那种方式,JVM都将当前帧弹出Java栈然后释放掉,这样上一个方法的帧就成为当前帧了。
Java栈上的所有数据都是此线程私有的,任何线程都不能访问另一个线程的栈数据,因此栈数据是线程安全的。
栈帧由3部分组成:局部变量区、操作数栈、帧数据区。
局部变量区和操作数栈是以字长(32位)为单位的数组。
局部变量区包含方法的参数和局部变量,编译器首先按把这些参数放入局部变量数组。Java栈帧的局部变量区被组织为一个以字长为单位,从0开始计数的数组。字节码指令通过从0开始的索引来使用其中的数据,如iload_1(把局部变量区的第2个变量压入栈顶),byte、short、int等的值在数组中只占据1项,而long、double的值在数组中占据连续的2项。
操作数栈:和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组,但是它不是通过索引来访问,而是通过标志的栈操作:压栈和出栈来访问的。如iadd指令,从操作数栈中弹出2个整数,执行加法运算,然后将其结果压回操作数栈。
iload_0 //将局部变量区的第1个变量压入栈
iload_1 //将局部变量区的第2个变量压入栈
iadd //栈中弹出2个整数,执行加法运算,然后将其结果压回操作数栈
istore_2 //将栈顶的整数出栈,并存入局部变量区的第3个变量
一般读取局部变量区的数据,需要把局部变量区的变量压入栈,
把值写到局部变量区,也需要先压入栈,再写到局部变量区
帧数据区:存放常量池(要访问的类、字段、方法名等),异常表等数据。
LineNumberTable:字节码偏移量与源代码之间的映射关系。
常见指令:iload_1, istore_1, iconst_1, ldc, bipush, pop, dup, iadd, isub, imul, idiv, return, goto,invoke…, new, newarray, arraylength, instanceof,athrow,monitorenter, monitorexit
然后用字节码了解一下JVM的语法糖:
语法糖:
泛型、自动装箱、自动拆箱、循环遍历、变长参数、条件编译、内部类、枚举类、断言语句、对枚举的switch
类型擦除:
public class TestCls3
{//编译失败,因为List<String>和List<Integer>的泛型被擦除,变成原生类型List
public static void method(List<String> list)
{
System.out.println("invoke method1");
}
public static void method(List<Integer> list)
{
System.out.println("invoke method2");
}
}
public class TestCls3
{//可以执行,因为在Class文件中,只有描述符不完全一致的两个方法就可以共存
//也就是说两个方法如果有相同的名称和特征签名,但返回值不同,也是可以共存在一个Class文件的
public static String method(List<String> list)
{
System.out.println("invoke method1");
return "";
}
public static int method(List<Integer> list)
{
System.out.println("invoke method2");
return 1;
}
public static void main(String[] args)
{
method(new ArrayList<String>());
method(new ArrayList<Integer>());
}
}
javap –verbose demo.TestCls3
Constant pool:
const #17 = Asciz (Ljava/util/List<Ljava/lang/String;>;)Ljava/lang/String;
;
const #39 = Asciz (Ljava/util/List<Ljava/lang/Integer;>;)I;
public static java.lang.String method(java.util.List); //方法名擦除为List
Signature: length = 0x2
00 11 //指到常量池中的第17
public static int method(java.util.List); //方法名擦除为List
Signature: length = 0x2
00 27 //指到常量池中的第39
类型擦除,仅仅对方法的Code属性中的字节码进行擦除,元数据Signature还是保留了泛型数据。(Method类的signature变量)
Java的条件编译
只有条件为常量且只有if语句才能有这种效果
public class TestCls5
{
public static void main(String[] args)
{
if (true)
{
System.out.println("true");
}
else
{
System.out.println("false");
}
}
}
编译后的字节码只包含:System.out.println("true");
public class TestCls5
{
public static void main(String[] args)
{
System.out.println("true");
}
}
自增++操作的线程非安全:
public class TestCls5
{
private static volatile int count;
public static void main(String[] args)
{
count++;
}
}
对应字节码(分为4个指令,在多线程下访问可能出现脏数据):
Code:
0: getstatic #18; //Field count:I 获取指定类的静态域,并将其值压入栈顶
3: iconst_1 将整型常量1压入栈顶
4: iadd 将栈顶的2个值出栈并相加,然后将结果入栈顶
5: putstatic #18; //Field count:I 为指定的类的静态域赋值
8: return 方法返回
字符串的+操作(javac编译器会对String连接做自动优化):
public String constractStr(String str1, String str2, String str3)
{
return str1 + str2 + str3;
}
对应字节码(JDK1.5之后转换为调用StringBuilder.append方法):
Code:
0: new #24; //class java/lang/StringBuilder
3: dup
4: aload_1
5: invokestatic #26; //Method java/lang/String.valueOf:(Ljava/lang/Objec
t;)Ljava/lang/String;
8: invokespecial #32; //Method java/lang/StringBuilder."<init>":(Ljava/la
ng/String;)V
11: aload_2
12: invokevirtual #35; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder;
15: aload_3
16: invokevirtual #35; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder; ――调用StringBuilder的append方法
19: invokevirtual #39; //Method java/lang/StringBuilder.toString:()Ljava/l
ang/String;
22: areturn ――返回引用
public String constractStr()
{
return "str1" + "str2" + "str3";
}
对应的字节码:
Code:
0: ldc #24; //String str1str2str3 --将字符串常量压入栈顶
2: areturn
public String constractStr(String str3)
{
return "str1" + "str2" + str3;
}
对应的字节码:
Code:
0: new #24; //class java/lang/StringBuilder
3: dup
4: ldc #26; //String str1str2 --将字符串常量str1str2压入栈顶
6: invokespecial #28; //Method java/lang/StringBuilder."<init>":(Ljava/la
ng/String;)V
9: aload_1
10: invokevirtual #31; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder; ――调用StringBuilder的append方法
13: invokevirtual #35; //Method java/lang/StringBuilder.toString:()Ljava/l
ang/String;
16: areturn
分享到:
相关推荐
Java 字节码简单说明 Java 字节码是 Java 跨平台的基础,它使得 Java 程序可以在不同的平台上运行,而不需要重新编译。Java 字节码是平台无关性的基石,也是语言无关性的基础。 Class 文件结构是 Java 字节码的...
为了深入理解Java字节码,文档中介绍了几个关键概念: 1. 魔数(Magic Number):class文件开头的“cafebabe”是Java字节码文件的魔数,用于标识一个文件是有效的Java字节码文件。这一点在内容部分被明确指出。 2....
Java字节码是Java编程语言的一个重要特性,它在Java程序执行过程中扮演着核心角色。本文将深入探讨Java字节码的基本概念、作用以及其在Java虚拟机(JVM)中的运行机制。 Java字节码是一种低级的、平台无关的指令集...
Java字节码分析工具,系统分析了java字节码文件,即java class类文件,对该文件中的各种成分以树的形式描述出来,只能针对未加密的class文件,一般由标准java编译器编译生成的class文件都未加密,该系统在vs2003下面...
Java字节码文件查看工具,如JD-GUI,是开发者们深入理解Java应用程序内部机制的重要辅助工具。这类工具能够帮助我们查看并分析.class文件,这些文件是Java源代码经过编译后的二进制形式,包含了运行时所需的所有指令...
javassist, Java字节码工程工具包 Java字节码工程工具包 版本 3版权所有( C ) 1999 -2017按 Shigeru Chiba,保留所有权利。Javassist ( Java编程助手) 使Java字节码操作简单。 它是一个类库,用于在Java中编辑字节码
【Java字节码结构解析】 Java程序在执行时,首先需要通过Java编译器将源代码(.java文件)编译成二进制的字节码文件(.class文件),这些字节码由Java虚拟机(JVM)解析并执行。深入理解字节码结构有助于我们了解...
**JAVA字节码操作库 BCEL** BCEL(Byte Code Engineering Library)是Java开发的一个重要工具,主要用于处理Java字节码。它为开发者提供了一种深入理解与操作Java类文件的底层机制,允许分析、创建、修改和优化字节...
Java字节码工程工具包Javassist 版本 3版权所有 (C) 1999-2023 Shigeru Chiba,保留所有权利。Javassist(JAVA 编程助手)使 Java 字节码操作变得简单。它是一个用于编辑 Java 字节码的类库它使 Java 程序能够在运行...
Java字节码文件反编译是一项重要的技术,它允许开发者查看和理解已编译的Java类文件(.class文件)内部的源代码结构。在Java中,源代码被编译成字节码,这是一种中间语言,由Java虚拟机(JVM)执行。然而,有时我们...
优秀的Java字节码可视化编辑工具,使用方便上手简单。
Java字节码是Java虚拟机(JVM)执行程序的核心组成部分,它是一种低级的、平台无关的指令集。ASM是一个开源的Java字节码操控和分析框架,它可以直接用来生成和修改Java类文件,是Java动态代理和字节码增强技术的重要...
本文将从Java字节码优化的角度,介绍Soot框架的应用和优化技术,并展示相关的应用实例。 Java语言的特点 Java语言具有以下特点: 1. 简单易学:Java语言的语法简单易学,易于开发者快速上手。 2. 解释执行:Java...
"classfinal"是一个开源的Java字节码加密工具,其目标是提供一种简单而有效的代码保护手段。该项目主要包含以下组件: 1. 源代码混淆器:对源代码进行混淆处理,使得反编译后的代码变得难以理解和分析。 2. 字节码...
如果用户使用源代码级API,他们可以在不了解Java字节码规范的情况下编辑类文件。整个API仅使用Java语言的词汇进行设计。您甚至可以以源文本的形式指定插入的字节码;Javassist 即时编译它。另一方面,字节码级API...
Java字节码反编译工具是开发者们在研究或调试Java程序时常用的一种辅助软件,它能够将已编译的Java字节码(.class文件)转换回可读性较高的源代码形式。这样的工具使得开发者可以查看和理解第三方库或者无法获取源代码...
jclasslib是一款免费开源的java字节码查看工具,该软件不但可以查看java字节码,同时还包含一个类库允许开发者读取,修改,写入Java Class文件与字节码。简单的说:用户可以通过jclasslib修改jar包下面的类,是一个...
`jclasslib`则是一款图形化的Java字节码浏览器,主要用于解析和展示Java字节码文件(`.class`文件)的内容。它可以直观地显示字节码指令和常量池中的信息,并且提供了一定程度上的编辑功能,使得用户可以直接在图形...