转载请注明出处:http://blog.csdn.net/linxdcn/article/details/73472553
1 概述
Java编译后的class文件格式如下定义:
ClassFile {
u4 magic;
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];
}
- magic:魔数,固定为0xCAFEBABE
- minor_version:次版本号
- major_version:主版本号
- constant_pool_count:常量池项个数
- constant_pool[constant_pool_count-1]:常量池表,长度为constant_pool_count-1
- access_flags:类或接口的访问标志
- this_class:当前类
- super_class:超类
- interfaces_count:接口数量
- interfaces[interfaces_count]:接口表
- fields_count:字段数量
- fields[fields_count]:字段表
- methods_count:方法数量
- methods[methods_count]:方法表
- attributes_count:属性数量
- attributes[attributes_count]:属性表
关于class文件中的各项说明,可以参考虚拟机规范或者底下参考文献的两篇文章,已经总结的非常的详细,本文对其中重要的几项作一个总结。
2 常量池
- 常量池保存了文件中类或接口相关的一切常量:
- 字面量:文字字符串、final变量值、基础数据类型
- 符号引用:如类或接口的全限定名、方法或字段的简单名称和描述符
根据虚拟机规范,常量池项目类型有以下分类
JVM规范规定的常量池项目类型
规范规定的总是很抽象的,下面举几个例子就好懂。
2.1 int a = 10
类型 | 常量池索引 | 值 | 备注 |
---|---|---|---|
CONSTANT_Integer | 1 | 10 | 字面量:基础数据类型 |
2.2 String a = “test”
类型 | 常量池索引 | 值 | 备注 |
---|---|---|---|
CONSTANT_String | 1 | #2 | 符号引用 |
CONSTANT_Utf8 | 2 | test | 字面量:文字字符串 |
2.3 Date a = new Date()
类型 | 常量池索引 | 值 | 备注 |
---|---|---|---|
CONSTANT_Class | 1 | #1 | 符号引用:类全限定名 |
CONSTANT_Utf8 | 2 | Ljava/util/Date; | 字面量:文字字符串 |
2.4 Field常量
在类中定义了field 字段,并且在类的其他地方(如方法中)使用到它,这是会用到Field常量,如以下代码
Class Test {
String name;
public void setName(String a) {
// 这里会使用Field常量
name = a; // 翻译成指令为: getfield #5
}
}
类型 | 常量池索引 | 值 | 备注 |
---|---|---|---|
CONSTANT_Class | 1 | #2 | 符号引用 |
CONSTANT_Utf8 | 2 | package/path/Test | 字面量:文字字符串 |
CONSTANT_Utf8 | 3 | name | 字面量:文字字符串 |
CONSTANT_Utf8 | 4 | Ljava/lang/String; | 字面量:文字字符串 |
CONSTANT_Fieldref | 5 | #1.#6 | 符号引用 |
CONSTANT_NameAndType | 6 | #3.#4 | 符号引用 |
2.5 Method常量
假如我们只定义了方法,但是这些方法没有在类总的其他地方被用到(如2.3中的代码),则这些方法引用信息并不会放到常量中。下面代码则可以使用到Method常量。
Class Test {
String name;
public String getName() {
return name;
}
public do() {
// 这里会使用Method常量
setName("test"); // 翻译成指令为: invokevirtual #5
}
}
类型 | 常量池索引 | 值 | 备注 |
---|---|---|---|
CONSTANT_Class | 1 | #2 | 符号引用 |
CONSTANT_Utf8 | 2 | package/path/Test | 字面量:文字字符串 |
CONSTANT_Utf8 | 3 | getName | 字面量:文字字符串 |
CONSTANT_Utf8 | 4 | ()java/lang/String; | 字面量:文字字符串 |
CONSTANT_Methodref | 5 | #1.#6 | 符号引用 |
CONSTANT_NameAndType | 6 | #3.#4 | 符号引用 |
3 字段表-field_info[]
首先需要说明的是,属性不仅在class文件结构中使用,在field_info和method_info中也使用。
在字段域出现的属性有ConstantValue(final常量)、Deprecated(被禁用的指示符)、Synthetic(编译器产生的指示符)。
4 方法表-method_info[]
方法域出现的属性有Code、Deprecated、Exceptions、Synthetic 。
(1)Code
Code类型的属性表(attribute_info)可以说是class文件中最为重要的部分,因为它包含的是JVM可以运行的机器码指令,JVM能够运行这个类,就是从这个属性中取出机器码的。
Code属性表包含:
- 机器指令—-code:
- 异常处理跳转信息—exception_table
- Java源码行号和机器指令的对应关系—LineNumberTable属性表
- 局部变量表描述信息—-LocalVariableTable属性表
5 一些思考
(1)常量池的CONSTANT_Fieldref
和跟字段域field_info
的区别?
CONSTANT_Fieldref,这只是一个字段的符号引用,通常作为字节码指令(opcode)getfield/getstatic/putfield/putstatic的操作数使用。
field_info是类中定义字段本身信息的描述,关于类加载过程中field部分的解析,以及其作为InstanceKlass类中成员属性的layout。
对于getfield/getstatic/putfield/putstatic四种与Field Reference相关的指令,在字段决议——resolve_field时,会有两者的协同使用——根据符号引用去_field中查找真正的字段。符号引用只是一个对应字段的一个字符串,符号引用经第一次解析(解析结果存入ConstantPoolCache),就会变成直接引用——field_offset。
对于CONSTANT_Methodref
和方法域method_info
也类似。
(2)如何防止class文件被劫持?
主要通过类加载双亲委派模型实现。
jvm首先会检查该类是否已经被加载,若没有被加载,则会委托父加载器进行装载,只有当父加载器无法加载时,才会调用自身的findClass()方法进行加载。这样避免了子加载器加载一些试图冒名顶替可信任类的不可靠类,也不会让子加载器去实现父加载器实现的加载工作。
并且jvm规定只有运行时包(同一个类加载器加载的、属于同一个包的多个类型集合),才能访问同一包内的类(和其子类)的protected成员。
(3)如何防止class文件反编译?
可以参考下这篇博客:http://blog.csdn.net/yuxiaohui78/article/details/8247096
6 参考
http://blog.csdn.net/luanlouis/article/details/39892027
http://www.cnblogs.com/iceAeterNa/p/4874197.html
转载请注明出处:http://blog.csdn.net/linxdcn/article/details/73472553