class文件是指以.class为文件后缀的Java虚拟机可装载文件。无论该class文件是在linux上进行编译的,还是在windows环境下编译的,无论虚拟机是在何种平台下实现和运行的,class文件使得Java虚拟机可以正确的读取、解释所有的class文件。 在分析和研究class文件之前,先提出有一些问题:
1.类/接口(class文件也可能定义的是接口,所以还是不要理解为类文件为好)内有哪些内容?
2.以上内容分别保存在class文件的什么地方?
3.这些内容在加载过程中又如何被读取和解析?
4.这些内容加载后又会被解析成为什么样的数据结构保存在虚拟机中?
5.这些数据结构在虚拟机的运行过程中又是如何被使用的?
扩展问题:
6.如何防止class文件被劫持?
7.如何防止class文件被反编译?
class文件的组织结构定义如下:
ClassFile{
magic? ? ? ? ? ? ? ? ? ? ? ? u4,
minor_version? ? ? ? ? ? ? ? u2,
major_version? ? ? ? ? ? ? ? u2,
constant_pool_count? ? ? ? ? ? u2,
constant_pool? ? ? ? ? ? ? ? cp_info*constant_pool_count,
access_flags? ? ? ? ? ? ? ? u2,
this_class? ? ? ? ? ? ? ? ? u2,
super_class? ? ? ? ? ? ? ? ? u2,
interface_count? ? ? ? ? ? ? u2,
interfaces? ? ? ? ? ? ? ? ? u2 * interface_count,
fields_count? ? ? ? ? ? ? ? u2,
fields? ? ? ? ? ? ? ? ? ? ? field_info * fields_count,
methods_count? ? ? ? ? ? ? ? u2,
methods? ? ? ? ? ? ? ? ? ? ? method_info * methods_count,
attributes_count? ? ? ? ? ? u2,
attributes? ? ? ? ? ? ? ? ? attributes_info * attributes_count
}
以如下程序为例,对生成的class文件进行分析:
//TestInterface.java
public interface TestInterface {
? ? public void interface_method();
}
//TestClass.java
public class TestClass implements TestInterface{
? ? private int private_global = 3;
? ? public int public_global;
? ? private static final int sfi = 127;
? ? public static final String sfs = "test strings";
? ? private StringBuilder sb;
? ?
? ? public void method_1(){
? ? ? ? private_global = public_global * 2;
? ? ? ? sb.append(private_global);
? ? }
? ?
? ? public void method_2(int pub){
? ? ? ? public_global = pub;
? ? }
? ? public void method_2(int pub, boolean flag){
? ? ? ? int tmp = 5;
? ? ? ? public_global = pub * 2 + tmp;
? ? }
? ?
? ? @Override
? ? public void interface_method() {
? ? ? ? method_1();
? ? }
? ?
}
1.magic(魔数) 值为0xcafebabe,没有特别的意义,放在文件头并选取用来标记改文件是一个class文件。

2.minor_version/major_version(次版本号和主版本号)

次版本号和主版本号分别为0x0000和0x0032(50),即主版本号位50,次版本号为0
3.constant_pool_count/constant_pool(常量池数量和常量池)
常量池保存了文件中类或接口相关的一切常量,字面常量(直接量),如文字字符串、final变量值,以及符号引用,如类或接口的全限定名、方法或字段的简单名称和描述符。
其中,全限定名用以在当前命名空间内唯一标志类或接口,在java语言中如java.lang.Object,在class文件中,会将'.'用'/'取代,即表示为java/lang/Object 简单名称就是简单的方法名或变量名的字符串,如java.lang.Object的成员方法wait()的简单名称为"wait"。
而只有简单名称是无法唯一确定调用的方法是哪一个,由于Java语言的特性,方法可能被重写或重载, 所以还需要根据方法的返回值、参数数量、类型、顺序来确定一个方法描述符来唯一标志该方法,字段的描述符则简单得多,只需要给出字段的类型 描述符让我们联想起PE/ELF文件的函数签名,它由上下文无关语法定义:
FieldDescriptor:
? ? ? ? ? ? FieldType
ComponentType:
? ? ? ? ? ? FieldType
FieldType:
? ? ? ? ? ? BaseType
? ? ? ? ? ? ObjectType
? ? ? ? ? ? ArrayType
BaseType:
? ? ? ? ? ? B
? ? ? ? ? ? C
? ? ? ? ? ? D
? ? ? ? ? ? F
? ? ? ? ? ? I
? ? ? ? ? ? J
? ? ? ? ? ? S
? ? ? ? ? ? Z
ObjectType:
? ? ? ? ? ? L;
ArrayType:
? ? ? ? ? ? [ComponentType
MethodDescriptor:
? ? ? ? ? ? (ParameterDescriptor*) ReturnDescriptor
ParameterDescriptor:
? ? ? ? ? ? FieldType
ReturnDescriptor:
? ? ? ? ? ? FieldType
? ? ? ? ? ? V
其终结符号如下:

以深入java虚拟机上的示例作为参考:
? ? ?

下面看class文件内常量池部分: 首先是常量池数:即(0x35)53个常量池

Java虚拟机将常量池组织成为列表(可以看做是一个常量池的数组)的形式,常量池内容可能指向其他常量池,并且class文件中其他部分内容也可能指向常量池入口,这些常量池通过该常量池在常量池列表中的索引来定位,常量池列表的0号常量池其实是空的,作为常量池的NULL引用,即常量池列表的第一项实际上是1号常量池,常量池列表实际上只有constant_pool_count - 1个常量池项。 随后是常量池列表,常量池的结构如下:
常量池的固定第一个字节是常量值标签,用来描述该常量池保存内容的类型,常量池标志和含义如下:
?
根据常量池标志tag的不同,info有不同的组织方式:
(1).CONSTANT_Utf8结构:
?
(可以看出length由2个字节表示,最大长度就应该是65536字节)
该类型是一个长度可变(长度为length)的常量字符串表,用来存储以下类型的字符串:
字符的存放:?
对于0x0001-0x007f的字符将使用一个字节(该字节的0-6位,第7位为0)存放?
对于0x080-0x07ff的字符将使用两个字节(依次高字节的0-5位和低字节的0-4位,剩余位分别为10、110)存放?

?
对于0x0800-0xffff的字符将使用3个字节(依