Java Class 位元組碼檔案結構詳解

拉風小野驢發表於2016-03-03

Class位元組碼中有兩種資料型別:

  1. 位元組資料直接量:這是基本的資料型別。共細分為u1、u2、u4、u8四種,分別代表連續的1個位元組、2個位元組、4個位元組、8個位元組組成的整體資料。
  2. 表:表是由多個基本資料或其他表,按照既定順序組成的大的資料集合。表是有結構的,它的結構體現在,組成表的成分所在的位置和順序都是已經嚴格定義好的。

Class位元組碼總體結構如下:

具體詳解請參考http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html

Class位元組碼檔案結構詳解

我在這裡要說明幾個細節問題:

  1. 為什麼說常量表的數量是constant_pool_count-1,且索引從1開始而不是0。其實根本原因在於,索引為0也是一個常量(保留常量),只不過它不存在常量表,這個常量就對應null值。因此加上這個系統保留常量,常量個數共為constant_pool_count個,但是常量表數量要減1。
  2. 在常量池中,如果存在long型或double型字面量,它們會佔用兩個連續索引。比如:假設一個類中只有一個int型字面量1和一個double型字面量1(當然這種假設是不可能的,因為總會有類名字面量等),則常量池個數為3,而不是2。這正是因為double字面量佔用了兩個連續的索引。

接下來,貼出一個小demo來展示如何讀取位元組碼:

ClassParser負責把握Class位元組碼整體結構的解析。

package com.lixin;

import java.io.IOException;
import java.io.InputStream;

public class ClassParser {

	private InputStream in;

	public ClassParser(InputStream in) {
		this.in = in;
	}

	public void parse() throws IOException {
		// 魔數
		magicNumber();
		// 主次版本號
		version();
		// 常量池
		constantPool();
		// 類或介面修飾符
		accessFlag();
		// 繼承關係(當前類、父類、父介面)
		inheritence();
		// 欄位集合
		fieldList();
		// 方法集合
		methodList();
		// 屬性集合
		attributeList();
	}

	private void attributeList() throws IOException {
		line();
		int attrLength = StreamUtils.read2(in);
		System.out.println("共有"+attrLength+"個屬性");
		for (int i=0;i<attrLength;i++) {
			line();
			attribute();
		}
	}
	private void attribute() throws IOException {
		int nameIndex = StreamUtils.read2(in); 
		int length = StreamUtils.read4(in); 
		byte[] info = StreamUtils.read(in, length);
		System.out.println("nameIndex:"+nameIndex);
		System.out.println("length:"+length);
		System.out.println("info:"+info);
	}

	private void methodList() throws IOException {
		int length = StreamUtils.read2(in);
		System.out.println("共有"+length+"個方法");
		for (int i=0;i<length;i++)
			method();
	}

	private void method() throws IOException {
		System.out.println("---------------------");
		int accessFlag = StreamUtils.read2(in);
		int nameIndex = StreamUtils.read2(in);
		int descriptorIndex = StreamUtils.read2(in);
		System.out.println("accessFlag:"+accessFlag);
		System.out.println("nameIndex:"+nameIndex);
		System.out.println("descriptorIndex:"+descriptorIndex);
		attributeList();
	}

	private void fieldList() throws IOException {
		line();
		int length = StreamUtils.read2(in);
		System.out.println("共有"+length+"個欄位");
		for (int i=0;i<length;i++) {
			System.out.println("-----------------------------");
			int accessFlag = StreamUtils.read2(in);
			int nameIndex = StreamUtils.read2(in);
			int descriptorIndex = StreamUtils.read2(in);
			System.out.println("accessFlag:"+accessFlag);
			System.out.println("nameIndex:"+nameIndex);
			System.out.println("descriptorIndex:"+descriptorIndex);
			attributeList();
		}
	}

	private void inheritence() throws IOException {
		line();
		int thisClassRef = StreamUtils.read2(in);
		int superClassRef = StreamUtils.read2(in);
		System.out.println("thisClassRef:"+thisClassRef);
		System.out.println("superClassRef:"+superClassRef);
		int interfaceLen = StreamUtils.read2(in);
		System.out.println("介面數量:"+interfaceLen);
		for (int i=0;i<interfaceLen;i++) {
			int interfaceRef = StreamUtils.read2(in);
			System.out.println("interfaceRef:"+interfaceRef);
		}
	}

	private void accessFlag() throws IOException {
		line();
		int accessFlag = StreamUtils.read2(in);
		System.out.println("accessFlag:0x"+Integer.toHexString(accessFlag)+"("+accessFlag+")");
	}

	private void constantPool() throws IOException {
		new ConstantPoolParser(in).constPool();
	}

	private void version() throws IOException {
		line();
		int minorVersion = StreamUtils.read2(in);
		int majorVersion = StreamUtils.read2(in);
		System.out.println("版本:"+majorVersion+"."+minorVersion);
	}

	private void magicNumber() throws IOException {
		line();
		int magic = StreamUtils.read4(in);
		System.out.println("魔數:"+Integer.toHexString(magic).toUpperCase());
	}

	private void line() {
		System.out.println("----------------------");
	}
}

ConstPoolParser負責常量池的解析(因為常量池表較多,且資料量也較大,因此單獨拉出來解析)

package com.lixin;

import java.io.IOException;
import java.io.InputStream;

public class ConstPoolParser {

	public static final int Utf8_info = 1;
	public static final int Integer_info = 3;
	public static final int Float_info = 4;
	public static final int Long_info = 5;
	public static final int Double_info = 6;
	public static final int Class_info = 7;
	public static final int String_info = 8;
	public static final int Fieldref_info = 9;
	public static final int Methodref_info = 10;
	public static final int InterfaceMethodref_info = 11;
	public static final int NameAndType_info = 12;
	public static final int MethodHandle_info = 15;
	public static final int MethodType_info = 16;
	public static final int InvokeDynamic_info = 18;

	private InputStream in;

	public ConstPoolParser(InputStream in) {
		this.in = in;
	}

	public void constPool() throws IOException {
		line();
		int length = StreamUtils.read2(in);
		System.out.println("共有"+length+"個常量");
		boolean doubleBytes = false;
		for (int i = 1; i < length; i++) {
			if (doubleBytes) {
				doubleBytes = false;
				continue;
			}
			line();
			System.out.println("常量索引:"+i);
			int flag = StreamUtils.read1(in);
//			System.out.println("標誌:"+flag);

			switch (flag) {
			case Utf8_info:
				utf8Info();
				continue;
			case Integer_info:
				integerInfo();
				continue;
			case Float_info:
				floatInfo();
				continue;
			case Long_info:
				doubleBytes = true;
				longInfo();
				continue;
			case Double_info:
				doubleBytes = true;
				doubleInfo();
				continue;
			case Class_info:
				classInfo();
				continue;
			case String_info:
				stringInfo();
				continue;
			case Fieldref_info:
				fieldrefInfo();
				continue;
			case Methodref_info:
				methodrefInfo();
				continue;
			case InterfaceMethodref_info:
				interfaceMethodrefInfo();
				continue;
			case NameAndType_info:
				nameAndTypeInfo();
				continue;
			case MethodHandle_info:
				methodHandleInfo();
				continue;
			case MethodType_info:
				methodTypeInfo();
				continue;
			case InvokeDynamic_info:
				invokeDynamicInfo();
				continue;
			default:
				System.err.println(flag);
				throw new RuntimeException("unknown");
			}
		}
	}

	private void line() {
		System.out.println("----------------------");
	}

	private void utf8Info() throws IOException {
		int length = StreamUtils.read2(in);
		byte[] buf = StreamUtils.read(in, length);
		String s = new String(buf,0,buf.length);
		System.out.println("utf8Info表:");
		System.out.println("值:"+s);
	}

	private void integerInfo() throws IOException {
		System.out.println("integerInfo表:");
		int value = StreamUtils.read4(in);
		System.out.println("值:"+value);
	}

	private void floatInfo() throws IOException {
		System.out.println("floatInfo表:");
		int value = StreamUtils.read4(in);
		float f = Float.intBitsToFloat(value);
		System.out.println("值:"+f);
	}

	private void longInfo() throws IOException {
		System.out.println("longInfo表:");
		long value = StreamUtils.read8(in);
		System.out.println("值:"+value);
	}

	private void doubleInfo() throws IOException {
		System.out.println("doubleInfo表:");
		long value = StreamUtils.read8(in);
		double d = Double.longBitsToDouble(value);
		System.out.println("值:"+d);

	}

	private void classInfo() throws IOException {
		System.out.println("classInfo表:");
		int index = StreamUtils.read2(in);
		System.out.println("index:" + index);
	}

	private void stringInfo() throws IOException {
		System.out.println("stringInfo表:");
		int index = StreamUtils.read2(in);
		System.out.println("index:" + index);
	}

	private void fieldrefInfo() throws IOException {
		int classIndex = StreamUtils.read2(in);
		int nameAndTypeIndex = StreamUtils.read2(in);
		System.out.println("fieldrefInfo表:");
		System.out.println("classIndex:" + classIndex);
		System.out.println("nameAndTypeIndex:" + nameAndTypeIndex);
	}

	private void methodrefInfo() throws IOException {
		int classIndex = StreamUtils.read2(in);
		int nameAndTypeIndex = StreamUtils.read2(in);
		System.out.println("methodrefInfo表:");
		System.out.println("classIndex:" + classIndex);
		System.out.println("nameAndTypeIndex:" + nameAndTypeIndex);
	}

	private void interfaceMethodrefInfo() throws IOException {
		int classIndex = StreamUtils.read2(in);
		int nameAndTypeIndex = StreamUtils.read2(in);
		System.out.println("interfaceMethodrefInfo表:");
		System.out.println("classIndex:" + classIndex);
		System.out.println("nameAndTypeIndex:" + nameAndTypeIndex);
	}

	private void nameAndTypeInfo() throws IOException {
		int nameIndex = StreamUtils.read2(in);
		int typeIndex = StreamUtils.read2(in);
		System.out.println("nameAndTypeInfo表:");
		System.out.println("nameIndex:" + nameIndex);
		System.out.println("typeIndex:" + typeIndex);
	}

	private void methodHandleInfo() throws IOException {
		int referenceKind = StreamUtils.read1(in);
		int referenceIndex = StreamUtils.read2(in);
		System.out.println("methodHandleInfo表:");
		System.out.println("referenceKind:"+referenceKind);
		System.out.println("referenceIndex:"+referenceIndex);
	}

	private void methodTypeInfo() throws IOException {
		System.out.println("methodTypeInfo表:");
		int descriptorIndex = StreamUtils.read2(in);
		System.out.println("descriptorIndex:"+descriptorIndex);
	}

	private void invokeDynamicInfo() throws IOException {
		int bootstrapMethodAttrIndex = StreamUtils.read2(in);
		int nameAndTypeIndex = StreamUtils.read2(in);
		System.out.println("bootstrapMethodAttrIndex:"+bootstrapMethodAttrIndex);
		System.out.println("nameAndTypeIndex:"+nameAndTypeIndex);
	}
}

StreamUtils負責從輸入位元組流中讀取資料

package com.lixin;

import java.io.IOException;
import java.io.InputStream;

public class StreamUtils {

	public static int read1(InputStream in) throws IOException {
		return in.read() & 0xff;
	}

	public static int read2(InputStream in) throws IOException{
		return (read1(in) << 8) | read1(in);
	}

	public static int read4(InputStream in) throws IOException {
		return (read2(in) <<16) | read2(in);
	}

	public static long read8(InputStream in) throws IOException {
		long high = read4(in) & 0xffffffffl;
		long low  = read4(in) & 0xffffffffl;
		return (high << 32) | (low);
	}

	public static byte[] read(InputStream in,int length) throws IOException {
		byte[] buf = new byte[length];
		in.read(buf, 0, length);
		return buf;
	}
}

TestClass為待解析的目標類,讀者可以任意改寫此類來多做實驗

package com.lixin;

public class TestClass {

	private int a = 5;
	protected char c = 'c';
	double x = 1.1;
	long y = 111;

	public void show() {

	}
}

測試方法入口:

package com.lixin;

import java.io.InputStream;

/**
 * 程式入口
 * @author lixin
 *
 */
public class App {

	public static void main(String[] args) throws Exception {
		InputStream in = Class.class.getResourceAsStream("/com/lixin/TestClass.class");
		ClassParser parser = new ClassParser(in);
		parser.parse();
	}

}

最後,我們可以使用jdk中的javap進行位元組碼反編譯,來對比我們的讀取與反編譯結果差別,用於查錯。

javap -v TestClass.class >./out.txt

相關文章