Class文件详解 (1)

博客展示了Java代码,先定义了TestClass类,编译后将其放在指定路径。接着用ClassAnalyzer类读取Test.class文件,分析其结构,重点对常量池进行了读取和分析,输出了魔数、版本号、常量池大小及各常量信息,目前只读取到常量池部分。
我们都知道,Java编译器负责将.java文件编译成.class文件,class文件存储的是java字节码,与.java文件无关(只要你愿意写一个编译器,也可以将别的语言写的源代码编译成.class文件),本文准备详细解剖class文件的内部结构,并且把class文件结构读取并显示出来。

Class文件的格式由JVM规范规定,一共有以下部分:
1. magic number,必须是0xCAFEBABE,用于快速识别是否是一个class文件。
2. version,包括major和minor,如果版本号超过了JVM的识别范围,JVM将拒绝执行。
3. constant pool,常量池,存放所有用到的常量。
4. access flag,定义类的访问权限。
5. this class和super class,指示如何找到this class和super class。
6. interfaces,存放所有interfaces。
7. fields,存放所有fields。
8. methods,存放所有methods。
9. attributes,存放所有attributes。

先写一个Test.java:

package example.test;

public final class TestClass {
public int id = 1234567;
public void test() {}
}

然后编译,放在C:\example\test\Test.class。

我们用Java来读取和分析class,ClassAnalyzer的功能便是读取Test.class,分析结构,然后显示出来:

package classfile.format;

import java.io.*;

public class ClassAnalyzer {

public static void main(String[] args) {
DataInputStream input = null;
try {
input = new DataInputStream(new BufferedInputStream(new FileInputStream(
"C:\\example\\test\\TestClass.class"
)));
analyze(input);
}
catch(Exception e) {
System.out.println("Analyze failed!");
}
finally {
try { input.close(); } catch(Exception e) {}
}
}

public static void analyze(DataInputStream input) throws IOException {
// read magic number:
int magic = input.readInt();
if(magic==0xCAFEBABE)
System.out.println("magic number = 0xCAFEBABE");
else
throw new RuntimeException("Invalid magic number!");
// read minor version and major version:
short minor_ver = input.readShort();
short major_ver = input.readShort();
System.out.println("Version = " + major_ver + "." + minor_ver);
// read constant pool:
short const_pool_count = input.readShort();
System.out.println("constant pool size = " + const_pool_count);
// read each constant:
for(int i=1; i<const_pool_count; i++) {
analyzeConstant(input, i);
}
}

public static void analyzeConstant(DataInputStream input, int index) throws IOException {
byte flag = input.readByte();
// for read:
byte n8;
short n16;
int n32;
long n64;
float f;
double d;
byte[] buffer;
System.out.println("\nconst index = " + index + ", flag = " + (int)flag);
switch(flag) {
case 1: // utf-8 string
System.out.println(" const type = Utf8");
n16 = input.readShort();
System.out.println(" length = " + n16);
buffer = new byte[n16];
input.readFully(buffer);
System.out.println(" value = " + new String(buffer));
break;
case 3: // integer
System.out.println(" const type = Integer");
n32 = input.readInt();
System.out.println(" value = " + n32);
break;
case 4: // float
System.out.println(" const type = Float");
f = input.readFloat();
System.out.println(" value = " + f);
break;
case 5: // long
System.out.println(" const type = Long");
n64 = input.readLong();
System.out.println(" value = " + n64);
break;
case 6: // double
System.out.println(" const type = Double");
d = input.readDouble();
System.out.println(" value = " + d);
break;
case 7: // class or interface reference
System.out.println(" const type = Class");
n16 = input.readShort();
System.out.println(" index = " + n16 + " (where to find the class name)");
break;
case 8: // string
System.out.println(" const type = String");
n16 = input.readShort();
System.out.println(" index = " + n16);
break;
case 9: // field reference
System.out.println(" const type = Fieldref");
n16 = input.readShort();
System.out.println("class index = " + n16 + " (where to find the class)");
n16 = input.readShort();
System.out.println("nameAndType = " + n16 + " (where to find the NameAndType)");
break;
case 10: // method reference
System.out.println(" const type = Methodref");
n16 = input.readShort();
System.out.println("class index = " + n16 + " (where to find the class)");
n16 = input.readShort();
System.out.println("nameAndType = " + n16 + " (where to find the NameAndType)");
break;
case 11: // interface method reference
System.out.println(" const type = InterfaceMethodref");
n16 = input.readShort();
System.out.println("class index = " + n16 + " (where to find the interface)");
n16 = input.readShort();
System.out.println("nameAndType = " + n16 + " (where to find the NameAndType)");
break;
case 12: // name and type reference
System.out.println(" const type = NameAndType");
n16 = input.readShort();
System.out.println(" name index = " + n16 + " (where to find the name)");
n16 = input.readShort();
System.out.println(" descripter = " + n16 + " (where to find the descriptor)");
break;
default:
throw new RuntimeException("Invalid constant pool flag: " + flag);
}
}
}

输出结果为:

magic number = 0xCAFEBABE
Version = 48.0
constant pool size = 22

const index = 1, flag = 1
const type = Utf8
length = 22
value = example/test/TestClass

const index = 2, flag = 7
const type = Class
index = 1 (where to find the class name)

const index = 3, flag = 1
const type = Utf8
length = 16
value = java/lang/Object

const index = 4, flag = 7
const type = Class
index = 3 (where to find the class name)

const index = 5, flag = 1
const type = Utf8
length = 2
value = id

const index = 6, flag = 1
const type = Utf8
length = 1
value = I

const index = 7, flag = 1
const type = Utf8
length = 6
value = <init>

const index = 8, flag = 1
const type = Utf8
length = 3
value = ()V

const index = 9, flag = 1
const type = Utf8
length = 4
value = Code

const index = 10, flag = 12
const type = NameAndType
name index = 7 (where to find the name)
descripter = 8 (where to find the descriptor)

const index = 11, flag = 10
const type = Methodref
class index = 4 (where to find the class)
nameAndType = 10 (where to find the NameAndType)

const index = 12, flag = 3
const type = Integer
value = 1234567

const index = 13, flag = 12
const type = NameAndType
name index = 5 (where to find the name)
descripter = 6 (where to find the descriptor)

const index = 14, flag = 9
const type = Fieldref
class index = 2 (where to find the class)
nameAndType = 13 (where to find the NameAndType)

const index = 15, flag = 1
const type = Utf8
length = 15
value = LineNumberTable

const index = 16, flag = 1
const type = Utf8
length = 18
value = LocalVariableTable

const index = 17, flag = 1
const type = Utf8
length = 4
value = this

const index = 18, flag = 1
const type = Utf8
length = 24
value = Lexample/test/TestClass;

const index = 19, flag = 1
const type = Utf8
length = 4
value = test

const index = 20, flag = 1
const type = Utf8
length = 10
value = SourceFile

const index = 21, flag = 1
const type = Utf8
length = 14
value = TestClass.java

我们暂时只读取到constant pool,后面的信息下次再继续 :)

参考:《Inside the Java Virture Mechine》

### .class 文件的生成 Java 源文件(`.java`)通过 Java 编译器(`javac`)编译后会生成 `.class` 文件。例如,有一个简单的 Java 源文件 `HelloWorld.java`: ```java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } ``` 使用 `javac HelloWorld.java` 命令编译后,会生成 `HelloWorld.class` 文件。 ### .class 文件的加载过程 当运行 Java 程序时,首先运行 JVM(Java 虚拟机),然后把 `.class` 文件加载到 JVM 的方法区里,接着在堆区创建一个 `java.lang.Class` 对象,用来封装类在方法区内的数据结构,并向程序员提供访问方法区内的数据结构的接口。在加载 `.class` 文件时,JVM 会先加载类中的所有静态成员(方法、变量、静态代码块)到方法区 `.class` 文件所处的静态区中。当把所有的静态成员加载完成之后,开始给类中的所有静态成员变量进行默认初始化。当类中的所有静态成员变量默认初始化之后,接着开始给所有静态成员变量显示赋值。当类中所有的静态成员变量显示赋值结束之后,静态代码块才会运行。当静态代码块执行结束之后,才表示 `.class` 文件加载完成[^3]。 ### .class 文件的查看方法 - **使用 `javap` 命令**:JDK 提供了 `javap` 命令用于反编译查看字节码内容。例如,要查看 `HelloWorld.class` 文件的字节码,可以使用命令 `javap -c HelloWorld.class`。 - **在 IntelliJ IDEA 中查看**:IntelliJ IDEA 作为流行的开发工具,为开发者提供了查看 `.class` 文件字节码的功能。可以通过编译输出目录查看 `.class` 文件[^1]。 - **使用 Java Bytecode Editor**:将 Java 代码编译成 `.class` 文件后,下载 Java Bytecode Editor,解压后直接运行 `jbe.sh` 即可看到 UI 面板,打开 `.class` 文件就能看到丰富的类信息,包括字节码内容。 - **反编译 APK 文件中的 `.class` 文件**:将要反编译的 APK 后缀名改为 `.rar` 或 `.zip` 并解压,得到其中的 `classes.dex` 文件(它是 Java 文件编译再通过 `dx` 工具打包而成的)。将获取到的 `classes.dex` 放到之前解压出来的工具 `dex2jar-2.0` 文件夹内,在此目录下输入 `cmd` 打开命令窗口,输入命令 `d2j-dex2jar.bat classes.dex`,在该目录下会生成一个 `classes_dex2jar.jar` 的文件。打开工具 `jd-gui` 文件夹里的 `jd-gui.exe`,用该工具打开之前生成的 `classes_dex2jar.jar` 文件,便可以看到源码,也可直接用 `jd-gui.exe` 工具打开 `.class` 文件查看[^2]。 ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值