最近看了周志明的深入java虚拟机,看到堆字节码解析这一章节,觉得特别的枯燥无味,于是我花了一段时间解析了一个class文件,由于方法体和属性这两项篇幅太长没做解析,想看这两个解析的略过。
class文件具有与语言无关性的特点,很多语言都可以编译成class文件,让jvm执行,class文件结构如下:
1.魔数
2.版本
3.常量池
4.访问符
5.类、超类、接口
6.字段
7.方法
8.属性
class文件的组成元素如下表:
类型 |
名称 |
数量 |
u4 |
magic |
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 |
attribute_count |
1 |
attribute_info |
attributes |
attributes_count |
1.常量池的结构
constant_pool_count u2
constant_pool cp_info
CONSTANT_Utf8 1 UTF-8编码的Unicode字符串
CONSTANT_Integer 3 int类型的字面值
CONSTANT_Float 4 float类型的字面值
CONSTANT_Long 5 long类型的字面值
CONSTANT_Double 6 double类型的字面值
CONSTANT_Class 7 对一个类或接口的符号引用
CONSTANT_String 8 String类型字面值的引用
CONSTANT_Fieldref 9 对一个字段的符号引用
CONSTANT_Methodref 10 对一个类中方法的符号引用
CONSTANT_InterfaceMethodref 11 对一个接口中方法的符号引用
CONSTANT_NameAndType 12 对一个字段或方法的部分符号引用
依次是常量池里对应的常量类型,编号,描述
字符常量,如一个类的名称,方法名称
CONSTANT_Utf8
tag 1
length u2
bytes[length]
整数常量
CONSTANT_Integer
tag 3
byte u4
String 类型的字面常量如:
public static final String name="xuehan";
CONSTANT_String
tag 8
string_index u2 (指向utf8的索引)
CONSTANT_NameAndType 对一个字段或方法的部分符号引用
tag 12
name_index u2 (名字,指向utf8)
descriptor_index u2 (描述符类型,指向utf8)
access flag u2:类的标示符
Flag NameValueInterpretation
ACC_PUBLIC0x0001public
ACC_FINAL0x0010final,不能被继承.
ACC_SUPER0x0020是否允许使用invokespecial指令,JDK1.2后,该值为true
ACC_INTERFACE0x0200是否是接口
ACC_ABSTRACT0x0400抽象类
ACC_SYNTHETIC0x1000该类不是由用户代码生成,运行时生成的,没有源码
ACC_ANNOTATION0x2000是否为注解
ACC_ENUM0x4000是否是枚举
this_class u2
指向常量池的Class
super_class u2
指向常量池的Class
interface_count u2
接口数量
interfaces
interface_count 个 interface u2
每个interface是指向CONSTANT_Class的索引
。。。。。以上还有很多我就不一一列出来了,下面解析一个class文件
需要解析的类
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
解析的代码:
package com.jvm.day7;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import util.BytesUtils;
/**
* 分析clas文件
* @Author:xuehan
* @Date:2016年3月27日上午10:26:54
*/
public class ClassAnalyze {
public void analyze(String filePath) throws Exception{
ByteBuffer buf = getBuffer(filePath);
System.out.println("魔数==>" + BytesUtils.toHexString(getbytes(buf, 4)));
buf = clearRead(buf, 4);
System.out.println("版本号==>" + BytesUtils.toHexString(getbytes(buf, 4)));
buf = clearRead(buf, 4);
System.out.println("常量池大小==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
System.out.println("第一个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) ));
// 读出第一个常量第一个index
buf = clearRead(buf, 1);
System.out.println("Methodref_info第一个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
// 读出第一个常量第二个index
buf = clearRead(buf, 2);
System.out.println("Methodref_info第二个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
// 读第二个常量
System.out.println("第二个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) ));
buf = clearRead(buf, 1);
System.out.println("fieldref_info第一个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
// 读出第一个常量第二个index
buf = clearRead(buf, 2);
System.out.println("fieldref_info第二个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
// 读第三个常量
System.out.println("第三个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) ));
buf = clearRead(buf,1);
System.out.println("class_info第一个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
// 读第四个常量
System.out.println("第四个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) ));
buf = clearRead(buf,1);
System.out.println("class_info第一个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
// 开始读取字符串常量
for(int i = 1; i < 13 ; i ++){
System.out.println("第" + i + "个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) ));
buf = clearRead(buf,1);
String hexStr = BytesUtils.toHexString(getbytes(buf, 2));
System.out.println("第" + i +" utf8_info length十六进制是==>" +hexStr);
int len = HexToInteger(hexStr);
System.out.println("第" + i +" utf8_info length十进制是==>" +len);
buf = clearRead(buf, 2);
System.out.println("第" + i + "utf8_info bytes是==>" + BytesUtils.toHexString(getbytes(buf, len)) + "字面常量是==>" + new String((getbytes(buf, len))));
buf = clearRead(buf, len);
}
System.out.println("已经读取16个常量,下面开始读取第17个常量#########################################");
// 读取第17个常量
System.out.println("第17个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) ));
buf = clearRead(buf,1);
System.out.println("class_info第1个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
System.out.println("class_info第2个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
// 读取第18个常量
System.out.println("第18个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) ));
buf = clearRead(buf,1);
System.out.println("class_info第1个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
System.out.println("class_info第2个index是==>" + BytesUtils.toHexString(getbytes(buf, 2)));
buf = clearRead(buf, 2);
// 读取第19和第20个常量
for(int i = 19; i < 21 ; i ++){
System.out.println("第" + i + "个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) ));
buf = clearRead(buf,1);
String hexStr = BytesUtils.toHexString(getbytes(buf, 2));
System.out.println("第" + i +" utf8_info length十六进制是==>" +hexStr);
int len = HexToInteger(hexStr);
System.out.println("第" + i +" utf8_info length十进制是==>" +len);
buf = clearRead(buf, 2);
System.out.println("第" + i + "utf8_info bytes是==>" + BytesUtils.toHexString(getbytes(buf, len)) + "字面常量是==>" + new String((getbytes(buf, len))));
buf = clearRead(buf, len);
}
// 常量池读取完毕读取acces_flags
System.out.println("acces_flags==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取this_class
System.out.println("this_class==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取super_class
System.out.println("super_class==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取interfaces_cout,如果cout大于0就要继续读取interfaces
System.out.println("interfaces_cout==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取fileds_count
System.out.println("fileds_count==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取fileds acces_flags
System.out.println("fileds acces_flags==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取fileds index
System.out.println("fileds index==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取fileds descriptor index
System.out.println("fileds descriptor index==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取attribute_count
System.out.println("attribute_count==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
// 读取methods_count
System.out.println("methods_count==>" + BytesUtils.toHexString(getbytes(buf, 2) ));
buf = clearRead(buf,2);
}
public static void main(String[] args) throws Exception {
ClassAnalyze ca = new ClassAnalyze();
ca.analyze("D:\\BaiduYunDownload\\data\\jvmtest\\User.class");
//ca.getBuffer("D:\\BaiduYunDownload\\data\\jvmtest\\User.class");
}
public ByteBuffer getBuffer(String filePath) throws Exception{
RandomAccessFile raf = new RandomAccessFile(filePath, "rw");
FileChannel channel = raf.getChannel();
ByteBuffer bb = ByteBuffer.allocate(2048);
channel.read(bb);
raf.close();
byte[] bbs = new byte[4];
bb.get(bbs);
return bb;
}
public byte[] getbytes(ByteBuffer bb, int len){
byte[] bytes =new byte[len];
for(int i =0; i < bytes.length; i ++){
bytes[i] = bb.get(i);
}
return bytes;
}
public int HexToInteger(String hexStr){
char[] chars = hexStr.toCharArray();
int secondInt = 0;
int thirdInt = 0;
if(chars[2] == 'a'){
secondInt = 10;
}else if(chars[2] == 'b'){
secondInt = 11;
}else if(chars[2] == 'c'){
secondInt = 12;
}else if(chars[2] == 'd'){
secondInt = 13;
}else if(chars[2] == 'e'){
secondInt = 14;
}else if(chars[2] == 'f'){
secondInt = 15;
}else{
secondInt = Integer.parseInt("" + chars[2]);
}
secondInt = 16 * secondInt;
if(chars[3] == 'a'){
thirdInt = 10;
}else if(chars[3] == 'b'){
thirdInt = 11;
}else if(chars[3] == 'c'){
thirdInt = 12;
}else if(chars[3] == 'd'){
thirdInt = 13;
}else if(chars[3] == 'e'){
thirdInt = 14;
}else if(chars[3] == 'f'){
thirdInt = 15;
}else{
thirdInt = Integer.parseInt("" + chars[3]);
}
return thirdInt + secondInt;
}
public ByteBuffer clearRead(ByteBuffer bb, int len){
System.out.println("清理之前==>" + BytesUtils.toHexString(bb.array()));
ByteBuffer bbnew = ByteBuffer.allocate(bb.capacity());
for(int i = len ; i < bb.capacity(); i ++){
bbnew.put(bb.get(i));
}
System.out.println("清理完毕==>" + BytesUtils.toHexString(bbnew.array()));
return bbnew;
}
}
魔数==>cafebabe
版本号==>00000033
常量池大小==>0015
第一个常量的tag==>0a
Methodref_info第一个index是==>0004
Methodref_info第二个index是==>0011
第二个常量的tag==>09
fieldref_info第一个index是==>0003
fieldref_info第二个index是==>0012
第三个常量的tag==>07
class_info第一个index是==>0013
第四个常量的tag==>07
class_info第一个index是==>0014
第1个常量的tag==>01
第1 utf8_info length十六进制是==>0004
第1 utf8_info length十进制是==>4
第1utf8_info bytes是==>6e616d65字面常量是==>name
第2个常量的tag==>01
第2 utf8_info length十六进制是==>0012
第2 utf8_info length十进制是==>18
第2utf8_info bytes是==>4c6a6176612f6c616e672f537472696e673b字面常量是==>Ljava/lang/String;
第3个常量的tag==>01
第3 utf8_info length十六进制是==>0006
第3 utf8_info length十进制是==>6
第3utf8_info bytes是==>3c696e69743e字面常量是==><init>
第4个常量的tag==>01
第4 utf8_info length十六进制是==>0003
第4 utf8_info length十进制是==>3
第4utf8_info bytes是==>282956字面常量是==>()V
第5个常量的tag==>01
第5 utf8_info length十六进制是==>0004
第5 utf8_info length十进制是==>4
第5utf8_info bytes是==>436f6465字面常量是==>Code
第6个常量的tag==>01
第6 utf8_info length十六进制是==>000f
第6 utf8_info length十进制是==>15
第6utf8_info bytes是==>4c696e654e756d6265725461626c65字面常量是==>LineNumberTable
第7个常量的tag==>01
第7 utf8_info length十六进制是==>0007
第7 utf8_info length十进制是==>7
第7utf8_info bytes是==>6765744e616d65字面常量是==>getName
第8个常量的tag==>01
第8 utf8_info length十六进制是==>0014
第8 utf8_info length十进制是==>20
第8utf8_info bytes是==>28294c6a6176612f6c616e672f537472696e673b字面常量是==>()Ljava/lang/String;
第9个常量的tag==>01
第9 utf8_info length十六进制是==>0007
第9 utf8_info length十进制是==>7
第9utf8_info bytes是==>7365744e616d65字面常量是==>setName
第10个常量的tag==>01
第10 utf8_info length十六进制是==>0015
第10 utf8_info length十进制是==>21
第10utf8_info bytes是==>284c6a6176612f6c616e672f537472696e673b2956字面常量是==>(Ljava/lang/String;)V
第11个常量的tag==>01
第11 utf8_info length十六进制是==>000a
第11 utf8_info length十进制是==>10
第11utf8_info bytes是==>536f7572636546696c65字面常量是==>SourceFile
第12个常量的tag==>01
第12 utf8_info length十六进制是==>0009
第12 utf8_info length十进制是==>9
第12utf8_info bytes是==>557365722e6a617661字面常量是==>User.java
已经读取16个常量,下面开始读取第17个常量#########################################
第17个常量的tag==>0c
class_info第1个index是==>0007
class_info第2个index是==>0008
第18个常量的tag==>0c
class_info第1个index是==>0005
class_info第2个index是==>0006
第19个常量的tag==>01
第19 utf8_info length十六进制是==>0004
第19 utf8_info length十进制是==>4
第19utf8_info bytes是==>55736572字面常量是==>User
第20个常量的tag==>01
第20 utf8_info length十六进制是==>0010
第20 utf8_info length十进制是==>16
第20utf8_info bytes是==>6a6176612f6c616e672f4f626a656374字面常量是==>java/lang/Object
acces_flags==>0021
this_class==>0003
super_class==>0004
interfaces_cout==>0000
fileds_count==>0001
fileds acces_flags==>0002
fileds index==>0005
fileds descriptor index==>0006
attribute_count==>0000
methods_count==>0003
完成的二进制编码: