java找出字符串不同处-c语言找出最大字符指针
不啰嗦,直接从最最简单的一段Java源代码开启Java整体字节码分析之旅。
Java 源码文件
package com.dskj.jvm.bytecode;
public class MyTest1 {
private int a = 1;
public intgetA {
return a;
}
public voidsetA(int a) {
this.a = a;
}
}Java字节码文件
IDEA工具编译代码后,Terminal 终端控制台,进入到生成class文件的目录下。
执行如下命令:
javap -verbose com.dskj.jvm.bytecode.MyTest1
生成字节码文件内容:
Classfile
/.../classes/com/dskj/jvm/bytecode/MyTest.class
Last modified Jul 31, 2018; size 489 bytes
MD5 checksum bdb537edd2d216ea99d6ce529073ee42
Compiled from "MyTest1.java"
public class com.dskj.jvm.bytecode.MyTest
minor version: 0
major version: 52 # JDK最大版本号
flags: ACC_PUBLIC, ACC_SUPER
Constant pool: #
#1 = Methodref #4.#20 // java/lang/Object."":V
#2 = Fieldref #3.#21 // com/dskj/jvm/bytecode/MyTest1.a:I
#3 = Class #22 // com/dskj/jvm/bytecode/MyTest1
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8
#8 = Utf8 V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/dskj/jvm/bytecode/MyTest1;
#14 = Utf8 getA
#15 = Utf8 I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 MyTest1.java
#20 = NameAndType #7:#8 // "":V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/dskj/jvm/bytecode/MyTest1
#23 = Utf8 java/lang/Object
{
public com.dskj.jvm.bytecode.MyTest1;
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: iconst_1
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 6: 0
line 8: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/dskj/jvm/bytecode/MyTest1;
public int getA;
descriptor: I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/dskj/jvm/bytecode/MyTest1;
public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 15: 0
line 16: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/dskj/jvm/bytecode/MyTest1;
0 6 1 a I
}
SourceFile: "MyTest1.java”Java字节码十六进制
Mac操作系统下建议使用 Hex Fiend 工具查看 MyTest1.class 文件的十六进制格式。
十六进制文本如下,便于后续分析使用:
CA FE BA BE 00 00 00 34 00 18 0A 00 04 00 14 09 00 03 00 15 07 00 16 07 00 17 01 00 01 61 01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 1F 4C 63 6F 6D 2F 64 73 6B 6A 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 31 3B 01 00 04 67 65 74 41 01 00 03 28 29 49 01 00 04 73 65 74 41 01 00 04 28 49 29 56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0C 4D 79 54 65 73 74 31 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 1D 63 6F 6D 2F 64 73 6B 6A 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 31 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 03 00 01 00 07 00 08 00 01 00 09 00 00 00 38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 06 00 04 00 08 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 0B 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 10 00 11 00 01 00 09 00 00 00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 0F 00 05 00 10 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D 00 00 00 00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00 13
Java字节码结构分析
前面都是铺垫,来到重磅分析的一节。
Java字节码整体结构如下图所示,以下图示以不同纬度展示了字节码结构中所包含的关键内容。
Java字节码整体结构图:
完整的Java字节码结构图:
接下来结合十六进制格式的 class 文件,参照 Java字节码文件来剖析下都包含了哪些内容。
1)4个字节,Magic Number
魔数,值为0xCAFEBABE,这是Java创始人James Gosling制定
2)2+2个字节,Version
包括minor_version和major_version,major_version:1.1(45),1.2(46),1.3(47),1.4(48),1.5(49),1.6(50),1.7(51),1.8(52),1.9(53),1.10(54)
3)2+n个字节,Constant Pool
包括字符串常量、数值常量等
4)2个字节,Access Flags
访问标记,标记当前的类是public、final、abstract等等,是不是满足某些特定要求。
5)2个字节,This Class Name
当前类的名字
6)2个字节,Super Class Name
当前类所属父类的名字
7)2+n个字节,Interfaces
当前类所实现的接口
8)2+n个字节,Fields
字段表,描述了当前类的字段的各种各样的信息
9)2+n个字节,Methods
方法表,当前类所定义的方法,这部分内容相对比以上字节结构是比较不容易理解
因为在我们一个类的定义当中方法是最常见的,方法里面包含若干的重要信息,包含签名、访问修饰符、名字、方法的执行代码逻辑、返回值等等。
这些方法也是以信息的形式存储在编译之后的字节码class文件当中,接下来,JVM去执行字节码文件时,当你调用某个特定方法时,JVM才能根据你所编写的源代码的意图去执行字节码里的指令。
对于这个方法来说,在JVM中最终是形成一条条指令的去执行的,也就是说在字节码里形成的每一条指令对应源码文件中的每一行源代码。
这些指令也可以称作为助记符,比如aload_0,iload_1等。
10)2+n个字节,Attributes
附加属性
Class字节码中有两种数据类型:
接下来,我们使用 javap -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、常量池、访问标记、类信息、类变量、类的成员变量、类的构造方法与类中的方法信息等信息。
4.1 魔数
魔数:所有的.class字节码文件的前4个字节都是魔数,文件中魔数为:CA FE BA BE,魔数值为固定值:0xCAFEBABE(咖啡宝贝?),这个值的获得很有“浪漫气息”,其作用是确定这个文件是否为一个能被虚拟机接受的Class文件。
4.2 版本号
版本号:魔数之后的4个字节为Class文件版本信息,前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。
这里的版本号为00 00 00 34,换算成十进制(3 * 16的1次方 + 4 = 52),表示次版本号为0,主版本号为52。
所以,该文件的版本号为:1.8.0。可以通过java -version命令来验证这一点。Java的版本号是从45开始的,JDK1.0之后大的主版本号线上加1,如JDK1.1(45)、JDK1.2(46)以此类推JDK1.8(52)。
4.3 常量池
常量池(constant pool):紧接着主版本号之后的就是常量池入口。
一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中。由于常量池中常量的数量是不固定的java找出字符串不同处,故在常量池入口需要放置一项u2类型的数据java找出字符串不同处,代表常量池容量计数值(constant_pool_count)。
这里的容量计数是从1开始的,十六进制数为:00 18,转换为十进制为24,代表常量池中有24项常量,索引值范围1~24。
常量池数组中元素的个数 = 常量池数 - 1(其中0暂时不使用),所以Java字节码文件中constant_pool中只看到了23项目常量。那为什么容量计数不从0开始呢?具体原因下一节说明。
常量池中主要存储两类常量:
4.3.1 常量池总体结构
Java类所对应的常量池主要由常量池数量与常量池数组(常量表)这两部分共同构成。
常量池数量紧跟在主版本号后面,占据2个字节;常量池数组紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中元素的类型、结构都是不同的,长度当然也就不同;但是,每一种元素第一个数据都是一个u1类型,该字节是个标志位,占据1个字节。
JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数 = 常量池数 - 1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达「不引用任何一个常量池」的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应值;所以,常量池的索引从1而非0开始。
4.3.2 常量池项目类型
Class文件结构中常量池中实际是有14种数据类型的,12~14种数据类型是在JDK1.7之后添加进来的(新增三种类型分别为:CONSTANT_MethodHandle_info、CONSTANT_MethodType_info、CONSTANT_InvokeDynamic_info),主要是为了更好的支持动态语言调用的。但是,最常用如下所列的列出了11种常规的数据类型:
上述常量都是以「CONSTANT」开头,以「info」结尾的常量名。每一个常量包含的信息的段都是不同的,我们可以根据每一个段自身的起始和结束位置是什么来进行分析。
抽出两个代表性的常量进行解析:
CONSTANT_Utf8_info
如果这个tag的值为1,占1个字节,它就表示的UTF-8编码的字符串;length,占2个字节,比如length值是4,表示的是从length的下后面读取4个字节长度的字符串。
这个就表示CONSTANT_Utf8_info的具体的文本内容。就是说根据length就能够知道接下来我要读取多少个字节才能读完,这些字节是由bytes来表示的。
CONSTANT_Fieldref_info
tag是U1类型,值为9。有两个index值,都是U2类型的,第一个index代表的是指向声明字段的类或接口描述符CONSTANT_Class_info的索引项,第二个index代表的指向字段描述符CONSTANT_NameAndType_info的索引项。
具体可以理解为当我们定义一个字段时,一定是附属在某一个类上的,所以要先索引到类信息上,可以具体看下CONSTANT_Class_info,其tag是U1类型,值为7,它的index代表指向全限定名常量项的索引,很好理解了。
然后再找到这个字段的描述符,这里指向了会索引到CONSTANT_NameAndType_info,其tag是U1类型,值为12,根据两个index的描述可以理解为要有字段或方法的名称以及字段或方法的描述符即可找到源码中对应的字段和方法。
4.3.3 常量池结构分析
接下来,我们以上述Java字节码结构总表为依据分析下Java字节码十六进制对应到Java字节码文件中的constant_pool常量池。
Java字节码十六进制:
从第9位开始的十六进制
0A 00 04 00 14 0A表示值为10,从字节码结构总表中找到值为10的是CONSTANT_Methodref_info,有两个index值,第一个index占用的字节 00 04 转换为十进制为4,第二个index占用的字节00 14 转化为十进制为20。
从Java字节码文件中Constant pool定义可看到:
Constant pool: #
#1 = Methodref #4.#20 // java/lang/Object."":V 索引到位置#4和#20,从常量池中找到这两个索引项如下:
#4 = Class #23 // java/lang/Object
#20 = NameAndType #7:#8 // "":V 这两个索引正好可以跟结构总表中对应上。其中,#4表示的类全限定名为java/lang/Object,而索引20位置又引用了#7:#8。继续找到#7和#8:
#7 = Utf8
#8 = Utf8 V从第16位开始的十六进制
09 00 03 00 15 这个标志位值为09,从字节码结构总表中找到值为9的常量为CONSTANT_Fieldref_info,其后面跟着两个index,对应十六进制转换为十进制为3和21。
#2 = Fieldref #3.#21 // com/dskj/jvm/bytecode/MyTest1.a:I