这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
简述
字节码文件
class文件本质上是一个以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在class文件中。jvm根据其特定的规则解析该二进制数据,从而得到相关信息。
Class文件采用一种伪结构来存储数据,它有两种类型:无符号数和表
Class文件的结构属性
| 类型 | 名称 | 数量 |
|---|---|---|
| u4 | magic(魔数)(0xCAFEBABE) | 1 |
| u2 | minor_version(次版本号) | 1 |
| u2 | major_version(主版本号) | 1 |
| u2 | constant_pool_count(常量计数器) | 1 |
| cp_info | constant_pool(常量池) | constant_pool_count-1 |
| u2 | access_flags(访问标志) | 1 |
| u2 | this_class(类索引) | 1 |
| u2 | super_class(父类索引) | 1 |
| u2 | interfaces_count(接口计数器) | 1 |
| u2 | interfaces | interfaces_count |
| u2 | fields_count | 1 |
| field_info | fields | fields_count |
| u2 | methods_count | 1 |
| method_info | methods | methods_count |
| u2 | attributes_count | 1 |
| attributes_info | attributes | attributes_count |
常量池
通用格式
//通用格式
cp_info {
u1 tag;
u1 info[];
}
tag信息
| 类型 | 标志(tag 区分类型) | 描述 |
|---|---|---|
| CONSTANT_Utf8_info | 1 | UTF-8 编码的字符串 |
| CONSTANT_Integer_info | 3 | 整性字面量 |
| CONSTANT_Float_info | 4 | 浮点型字面量 |
| CONSTANT_Long_info | 5 | 长整型字面量 |
| CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
| CONSTANT_Class_info | 7 | 类或则接口的符号引用 |
| CONSTANT_String_info | 8 | 字符串类型字面量 |
| CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
| CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
| CONSTANT_InterfaceMethodref_info | 11 | 接口中的方法符号引用 |
| CONSTANT_NameAndType_info | 12 | 字段或者方法的部分符号引用 |
| CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
| CONSTANT_MethodType_info | 16 | 标识方法类型 |
| CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法的调用点 |
访问标志
| 标志名称 | 标志值 | 含义 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 是否为public |
| ACC_FINAL | 0x0010 | 是否被声明为final,只有类可设置 |
| ACC_SUPER | 0x0020 | 是否允许使用invokespecial 字节码指令得新语意,invokespecial指令得语意在JDK 1.0.2发生改变,为了区别这条指令使用哪种语意,在JDK1.0.2之后便宜出来的类这个标志是真 |
| ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
| ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假 |
| ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生 |
| ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
| ACC_ENUM | 0x4000 | 标识这是一个枚举 |
字段表集合
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | access_flags | 1 |
| u2 | name_index | 1 |
| u2 | descriptor_index | 1 |
| u2 | attributes_count | 1 |
| attribute_info | attributes | attributes_count-1 |
access_flags
| 标志名称 | 标志值 | 含义 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 字段是否public |
| ACC_PRIVATE | 0x0002 | 字段是否private |
| ACC_PROTECTED | 0x0004 | 字段是否protected |
| ACC_STATIC | 0x0008 | 字段是否static |
| ACC_FINAL | 0x0010 | 字段是否final |
| ACC_VOLATILE | 0x0040 | 字段是否volatile |
| ACC_TRABSIENT | 0x0080 | 字段是否transient |
| ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动产生 |
| ACC_ENUM | 0x4000 | 字段是否enum |
name_index和descriptor_index
它们都是对常量 池的引用,分别代表着字段的简单名称以及字段和方法的描述符。
简单名称是指没有类型和参数修饰的方法或者字段名称,这个类 中的inc()方法和m字段的简单名称分别是“inc”和“m”。
描述符的作用是 用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描 述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表 无返回值的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”。
属性表集合
属性表通用结构
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | attribute_name_index | 1 |
| u4 | attribute_length | 1 |
| u1 | info | attribute_length |
虚拟机规范预定义的属性(具体相关属性请查阅相关文档)
| 属性名称 | 使用位置 | 含义 |
|---|---|---|
| Code | 方法表 | Java代码编译成的字节码指令 |
| ConstantValue | 字段表 | 由final关键字定义的常量值 |
| Deprecated | 类、方法表、字段 | 被声明为deprecated的方法和字段 |
| Exceptions | 方法表 | 方法抛出的异常列表 |
| EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
| InnerClass | 类文件 | 内部类列表 |
| LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
| LocalVariableTable | Code属性 | 方法的局部变量描述 |
| StackMapTable | Code属性 | JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配 |
| Signature | 类、方法表、字段 | 用于支持泛型情况下的方法签名 |
| SourceFile | 类文件 | 记录源文件名称 |
| SourceDebugExtension | 类文件 | 用于存储额外的调试信息 |
| Synthetic | 类、方法表、字段 | 标志方法或字段为编译器自动生成的 |
| LocalVariableTypeTable | 类 | 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 |
| RuntimeVisibleAnnotations | 类、方法表、字段 | 为动态注解提供支持 |
| RuntimeInvisibleAnnotations | 类、方法表、字段 | 用于指明哪些注解是运行时不可见的 |
| RuntimeVisibleParameterAnnotations | 方法表 | 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法 |
| RuntimeInvisibleParameterAnnotations | 方法表 | 作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数 |
| AnnotationDefault | 方法表 | 用于记录注解类元素的默认值 |
| BootstrapMethos | 类文件 | 用于保存invokeddynamic指令引用的引导方式限定符 |
| RuntimeVisibleTypeAnnotations | 类、方法表、字段、Code属性 | 指明哪些注解运行时可见的 |
| RuntimeInvisibleTypeAnnotations | 类、方法表、字段、Code属性 | 指明哪些注解运行时不可见的 |
| MethodParameters | 方法表 | 用于支持(编译时加上-parameters参数)将方法名称编译进class文件,并在运行中获取 |
| Module | 类 | 用于记录一个Module名称及相关信息 |
| ModulePackages | 类 | 用于记录一个模块中被exports 或则opens的包 |
| ModuleMainClass | 类 | 指定一个模块的指令 |
| NestHost | 类 | 用于支持嵌套类的反射和访问控制API,一个内部类通过该属性得知自己的宿主类 |
| NestMembers | 类 | 用于支持嵌套类的反射和访问控制API,一个宿主类通过该属性得知自己有哪些内部类 |
代码实现
ClassReader
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* @Author blackcat
* @create 2021/8/12 8:20
* @version: 1.0
* @description:读取class数据
*
* java虚拟机定义了u1、u2、u4三种数据类型来表示;1字节、2字节、4字节,无符号整数。
*/
public class ClassReader {
private byte[] codes;
private int pos;
public ClassReader(byte[] codes) {
this.codes = codes;
this.pos = 0;
}
//u1
public int nextU1toInt() {
return ByteUtil.byteToInt(new byte[] { codes[pos++] });
}
//u2
public int nextU2ToInt() {
return ByteUtil.byteToInt(new byte[] { codes[pos++], codes[pos++] });
}
//u4
public int nextU4ToInt() {
return ByteUtil.byteToInt(new byte[] { codes[pos++], codes[pos++], codes[pos++], codes[pos++] });
}
//u4 转String
public String nextU4ToHexString() {
return ByteUtil.byteToHexString((new byte[] { codes[pos++], codes[pos++], codes[pos++], codes[pos++] }));
}
public long next2U4ToLong() {
byte[] bytes = nextBytes(8);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getLong();
}
public double next2U4Double() {
byte[] bytes = nextBytes(8);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getDouble();
}
public float nextU4ToFloat() {
byte[] bytes = nextBytes(4);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
}
public int[] nextUint16s() {
int count = nextU2ToInt();
int[] result = new int[count];
for (int i = 0; i < count; i++) {
result[i] = nextU2ToInt();
}
return result;
}
//读取长度为len的数据
public byte[] nextBytes(int len) {
if (pos + len >= codes.length) {
throw new ArrayIndexOutOfBoundsException();
}
byte[] data = Arrays.copyOfRange(codes, pos, pos + len);
pos += len;
return data;
}
}
ClassFile
import com.black.cat.jvm.classfile.attribute.AttributeInfo;
import com.black.cat.jvm.classfile.attribute.BaseAttributeInfo;
import com.black.cat.jvm.classfile.constantpool.ConstantPool;
import com.black.cat.jvm.common.CommonValue;
import lombok.Data;
/**
* @Author blackcat
* @create 2021/8/12 8:25
* @version: 1.0
* @description: class 文件
*
*ClassFile {
* u4 magic; 魔数(0xCAFEBABE)
* u2 minor_version;次版本号
* u2 major_version;主版本号
* u2 constant_pool_count;常量计数器
* cp_info constant_pool[constant_pool_count-1];常量池
* u2 access_flags;访问标志
* u2 this_class;类索引
* u2 super_class;父类索引
* u2 interfaces_count;接口计数器
* u2 interfaces[interfaces_count];接口表
* u2 fields_count;字段计数器
* field_info fields[fields_count];字段表
* u2 methods_count;方法计数器
* method_info methods[methods_count];方法表
* u2 attributes_count;属性计数器
* attribute_info attributes[attributes_count];属性表
* }
*
*/
@Data
public class ClassFile {
private ClassReader reader;
private String magic;
private int minorVersion;
private int majorVersion;
// private int constantPoolCount;
private ConstantPool constantPool;
private int accessFlag;
private int classNameIndex;
private int superClassNameIndex;
// private int interfaceCount;
private int[] interfaceIndexes;
// private int fieldCount;
private MemberInfo[] fieldInfos;
// private int methodCount;
private MemberInfo[] methodInfos;
// private int attributeCount;
private AttributeInfo[] attributeInfos;
public ClassFile(byte[] bytes) {
this.reader = new ClassReader(bytes);
readAndCheckMagic();
readAndCheckVersion();
readConstantPool();
readAccessFlag();
readClassNameIndex();
readSuperClassNameIndex();
readInterfaceIndexes();
readFields();
readMethods();
readAttributes();
}
private void readAndCheckMagic() {
String magic = reader.nextU4ToHexString();
this.magic = magic;
if (!"cafebabe".equalsIgnoreCase(magic)) {
throw new ClassFormatError("magic!");
}
}
private void readAndCheckVersion() {
this.minorVersion = this.reader.nextU2ToInt();
this.majorVersion = this.reader.nextU2ToInt();
// jdk11对应的major为55,支持jdk1.2-jdk11
if (this.majorVersion >= CommonValue.JDK_VERSION_MAJOR_02
&& this.majorVersion <= CommonValue.JDK_VERSION_MAJOR_11
&& this.minorVersion == CommonValue.JDK_VERSION_MINOR_00) {
return;
}
throw new UnsupportedClassVersionError();
}
private void readConstantPool() {
this.constantPool = new ConstantPool(this.reader);
}
private void readAccessFlag() {
this.accessFlag = this.reader.nextU2ToInt();
}
private void readClassNameIndex() {
this.classNameIndex = this.reader.nextU2ToInt();
}
private void readSuperClassNameIndex() {
this.superClassNameIndex = this.reader.nextU2ToInt();
}
private void readInterfaceIndexes() {
this.interfaceIndexes = reader.nextUint16s();
}
private void readFields() {
fieldInfos = MemberInfo.readMembers(constantPool, reader);
}
private void readMethods() {
methodInfos = MemberInfo.readMembers(constantPool, reader);
}
private void readAttributes() {
this.attributeInfos = BaseAttributeInfo.readAttributes(reader, constantPool);
}
}
剩下请参考
测试
public class Main {
public static void main(String[] args) {
String[] argv = {"-classpath", "D:\\develop\\code\\jjvm\\jvm-03\\target\\classes", "com.black.cat.jvm.MainTest"};
Cmd cmd = Cmd.parse(argv);
if (!cmd.ok || cmd.helpFlag) {
System.out.println("Usage: <main class> [-options] class [args...]");
return;
}
if (cmd.versionFlag) {
System.out.println("java version \"1.8.0\"");
return;
}
startJVM(cmd);
}
private static void startJVM(Cmd cmd) {
System.out.printf("classpath:%s class:%s args:%s\n", cmd.classpath, cmd.getMainClass(), cmd.getAppArgs());
Classpath classpath = new Classpath(null, cmd.classpath);
String className = cmd.getMainClass().replace(".", "/");
ClassFile classFile = loadClass(className, classpath);
assert classFile != null;
printClassInfo(classFile);
}
private static ClassFile loadClass(String className, Classpath classpath) {
try {
byte[] classData = classpath.readClass(className);
return new ClassFile(classData);
} catch (Exception e) {
System.out.println("Could not find or load main class " + className);
return null;
}
}
//使用 javap -verbose MainTest查看所有常量池进行比对
private static void printClassInfo(ClassFile cf) {
System.out.println("version: " + cf.getMajorVersion() + "." + cf.getMinorVersion());
System.out.println("constants count:" + cf.getConstantPool().getSize());
for (int i = 1; i < cf.getConstantPool().getSize(); i++) {
System.out.println("constants" + i + ":" + cf.getConstantPool().getConstantInfos()[i]);
}
System.out.format("access flags:0x%x\n", cf.getAccessFlag());
System.out.println("this classindex:" + cf.getClassNameIndex());
System.out.println("this class:" + cf.getConstantPool().getUtfStringByIndex(cf.getClassNameIndex()));
System.out.println("super classindex:" + cf.getSuperClassNameIndex());
System.out.println("super class:" + cf.getConstantPool().getUtfStringByIndex(cf.getSuperClassNameIndex()));
System.out.println("interfaceCount:" + cf.getInterfaceIndexes().length);
for (int interfaceIndex : cf.getInterfaceIndexes()) {
System.out.println("interfaces:" + cf.getConstantPool().getUtfStringByIndex(interfaceIndex));
}
System.out.println("fields count:" + cf.getFieldInfos().length);
for (MemberInfo memberInfo : cf.getFieldInfos()) {
System.out.format(" %s\n", memberInfo.toString());
}
System.out.println("methods count: " + cf.getMethodInfos().length);
for (MemberInfo memberInfo : cf.getMethodInfos()) {
System.out.format(" %s\n", memberInfo.toString());
}
System.out.println("attribute count: " + cf.getAttributeInfos().length);
for (AttributeInfo attributeInfo : cf.getAttributeInfos()) {
System.out.format(" %s\n", attributeInfo.toString());
}
}
}
public class MainTest {
public static final boolean FLAG = true;
public static final byte BYTE = 123;
public static final char X = 'X';
public static final short SHORT = 12345;
public static final int INT = 123456789;
public static final long LONG = 12345678901L;
public static final float PI = 3.14f;
public static final double E = 2.71828;
public static void main(String[] args) throws IOException {
System.out.println("Hello World");
main2(args);
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
System.out.println(sum);
}
public static float main2(String[] args) {
float pi = 3.14F;
float r = 5.4F;
float area = 2 * pi * r;
return area;
}
}
结果
可使用 javap -verbose MainTest查看所有常量池进行比对
version: 52.0
constants count:78
constants1:10--------------java/lang/Object.<init>:()V
constants2:9--------------java/lang/System.out:Ljava/io/PrintStream;
constants3:8--------------Hello World
constants4:10--------------java/io/PrintStream.println:(Ljava/lang/String;)V
constants5:10--------------com/black/cat/jvm/MainTest.main2:([Ljava/lang/String;)F
constants6:10--------------java/io/PrintStream.println:(I)V
constants7:4--------------3.14
constants8:4--------------5.4
constants9:7--------------com/black/cat/jvm/MainTest
constants10:7--------------java/lang/Object
constants11:1--------------FLAG
constants12:1--------------Z
constants13:1--------------ConstantValue
constants14:3--------------1
constants15:1--------------BYTE
constants16:1--------------B
constants17:3--------------123
constants18:1--------------X
constants19:1--------------C
constants20:3--------------88
constants21:1--------------SHORT
constants22:1--------------S
constants23:3--------------12345
constants24:1--------------INT
constants25:1--------------I
constants26:3--------------123456789
constants27:1--------------LONG
constants28:1--------------J
constants29:5--------------12345678901
constants30:null
constants31:1--------------PI
constants32:1--------------F
constants33:1--------------E
constants34:1--------------D
constants35:6--------------2.71828
constants36:null
constants37:1--------------<init>
constants38:1--------------()V
constants39:1--------------Code
constants40:1--------------LineNumberTable
constants41:1--------------LocalVariableTable
constants42:1--------------this
constants43:1--------------Lcom/black/cat/jvm/MainTest;
constants44:1--------------main
constants45:1--------------([Ljava/lang/String;)V
constants46:1--------------i
constants47:1--------------args
constants48:1--------------[Ljava/lang/String;
constants49:1--------------sum
constants50:1--------------StackMapTable
constants51:1--------------Exceptions
constants52:7--------------java/io/IOException
constants53:1--------------main2
constants54:1--------------([Ljava/lang/String;)F
constants55:1--------------pi
constants56:1--------------r
constants57:1--------------area
constants58:1--------------SourceFile
constants59:1--------------MainTest.java
constants60:12--------------<init>:()V
constants61:7--------------java/lang/System
constants62:12--------------out:Ljava/io/PrintStream;
constants63:1--------------Hello World
constants64:7--------------java/io/PrintStream
constants65:12--------------println:(Ljava/lang/String;)V
constants66:12--------------main2:([Ljava/lang/String;)F
constants67:12--------------println:(I)V
constants68:1--------------com/black/cat/jvm/MainTest
constants69:1--------------java/lang/Object
constants70:1--------------java/io/IOException
constants71:1--------------java/lang/System
constants72:1--------------out
constants73:1--------------Ljava/io/PrintStream;
constants74:1--------------java/io/PrintStream
constants75:1--------------println
constants76:1--------------(Ljava/lang/String;)V
constants77:1--------------(I)V
access flags:0x21
this classindex:9
this class:com/black/cat/jvm/MainTest
super classindex:10
super class:java/lang/Object
interfaceCount:0
fields count:8
MemberInfo{accessFlags=25, nameIndex=11, descriptorIndex=12, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=14}]}
MemberInfo{accessFlags=25, nameIndex=15, descriptorIndex=16, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=17}]}
MemberInfo{accessFlags=25, nameIndex=18, descriptorIndex=19, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=20}]}
MemberInfo{accessFlags=25, nameIndex=21, descriptorIndex=22, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=23}]}
MemberInfo{accessFlags=25, nameIndex=24, descriptorIndex=25, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=26}]}
MemberInfo{accessFlags=25, nameIndex=27, descriptorIndex=28, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=29}]}
MemberInfo{accessFlags=25, nameIndex=31, descriptorIndex=32, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=7}]}
MemberInfo{accessFlags=25, nameIndex=33, descriptorIndex=34, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=35}]}
methods count: 3
MemberInfo{accessFlags=1, nameIndex=37, descriptorIndex=38, attributes=[CodeAttribute{attributeLength=47maxStack=1, maxLocals=1, code=[42, -73, 0, 1, -79], attributes=[LineNumberTableAttribute{lineNumberTable=[LineNumberTableEntry{,startPc=0, lineNumber=11}]}, LocalVariableTableAttribute{localVariableTable=[LineNumberTableEntry{,startPc=0, length=5,nameIndex=42, descriptorIndex=43, index=0}]}], exceptionTable=[]}]}
MemberInfo{accessFlags=9, nameIndex=44, descriptorIndex=45, attributes=[CodeAttribute{attributeLength=147maxStack=2, maxLocals=3, code=[-78, 0, 2, 18, 3, -74, 0, 4, 42, -72, 0, 5, 87, 3, 60, 4, 61, 28, 16, 10, -93, 0, 13, 27, 28, 96, 60, -124, 2, 1, -89, -1, -13, -78, 0, 2, 27, -74, 0, 6, -79], attributes=[LineNumberTableAttribute{lineNumberTable=[LineNumberTableEntry{,startPc=0, lineNumber=23}, LineNumberTableEntry{,startPc=8, lineNumber=24}, LineNumberTableEntry{,startPc=13, lineNumber=26}, LineNumberTableEntry{,startPc=15, lineNumber=27}, LineNumberTableEntry{,startPc=23, lineNumber=28}, LineNumberTableEntry{,startPc=27, lineNumber=27}, LineNumberTableEntry{,startPc=33, lineNumber=30}, LineNumberTableEntry{,startPc=40, lineNumber=31}]}, LocalVariableTableAttribute{localVariableTable=[LineNumberTableEntry{,startPc=17, length=16,nameIndex=46, descriptorIndex=25, index=2}, LineNumberTableEntry{,startPc=0, length=41,nameIndex=47, descriptorIndex=48, index=0}, LineNumberTableEntry{,startPc=15, length=26,nameIndex=49, descriptorIndex=25, index=1}]}, com.black.cat.jvm.classfile.attribute.UnparsedAttribute@1dd02175], exceptionTable=[]}, com.black.cat.jvm.classfile.attribute.ExceptionsAttribute@31206beb]}
MemberInfo{accessFlags=9, nameIndex=53, descriptorIndex=54, attributes=[CodeAttribute{attributeLength=98maxStack=2, maxLocals=4, code=[18, 7, 68, 18, 8, 69, 13, 35, 106, 36, 106, 70, 37, -82], attributes=[LineNumberTableAttribute{lineNumberTable=[LineNumberTableEntry{,startPc=0, lineNumber=34}, LineNumberTableEntry{,startPc=3, lineNumber=35}, LineNumberTableEntry{,startPc=6, lineNumber=36}, LineNumberTableEntry{,startPc=12, lineNumber=37}]}, LocalVariableTableAttribute{localVariableTable=[LineNumberTableEntry{,startPc=0, length=14,nameIndex=47, descriptorIndex=48, index=0}, LineNumberTableEntry{,startPc=3, length=11,nameIndex=55, descriptorIndex=32, index=1}, LineNumberTableEntry{,startPc=6, length=8,nameIndex=56, descriptorIndex=32, index=2}, LineNumberTableEntry{,startPc=12, length=2,nameIndex=57, descriptorIndex=32, index=3}]}], exceptionTable=[]}]}
attribute count: 1
SourceFileAttribute{attributeLength=2sourceFile=MainTest.java}