Class字节码中有两种数据类型:

  1. 字节数据直接量:这是基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据。
  2. 表:表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的,它的结构体现在,组成表的成分所在的位置和顺序都是已经严格定义好的。

Class字节码总体结构如下:

具体详解请参考http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html

我在这里要说明几个细节问题:

  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

Java Class 字节码文件结构详解的更多相关文章

  1. Kotlin伴生对象及其字节码内幕详解

    继续面向对象,开撸就是!! 接口: 我们知道对于JDK8之后接口中除了方法的声明之后还可以有default方法的,而在Kotlin中也类似,下面来看一下在Kotlin接口相关的东东: 很显然就是一个方 ...

  2. 从 HelloWorld 看 Java 字节码文件结构

    很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...

  3. Java之函数式接口@FunctionalInterface详解(附源码)

    Java之函数式接口@FunctionalInterface详解 函数式接口的定义 在java8中,满足下面任意一个条件的接口都是函数式接口: 1.被@FunctionalInterface注释的接口 ...

  4. Java字节码文件结构剖析

    今天起开启JVM的新的知识学习篇章----Java的字节码,那学习Java字节码有啥用呢?我们知道Java是跨平台的一门语言,编写一次到处运行,而支撑着这个特性的根基为两点:JVM和.class字节码 ...

  5. <JVM中篇:字节码与类的加载篇>01-Class字节码文件结构

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  6. Java虚拟机之垃圾回收详解一

    Java虚拟机之垃圾回收详解一 Java技术和JVM(Java虚拟机) 一.Java技术概述: Java是一门编程语言,是一种计算平台,是SUN公司于1995年首次发布.它是Java程序的技术基础,这 ...

  7. Java I/O : Java中的进制详解

    作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层. ...

  8. Java网络编程和NIO详解开篇:Java网络编程基础

    Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...

  9. Java网络编程和NIO详解8:浅析mmap和Direct Buffer

    Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NI ...

随机推荐

  1. 设置MATLAB中figure的背景为白色

    matlab的图形窗口每次背景都是灰色的,而我希望每次都是白色的背景,方便用图: 每次总是需要添加figure('color','w');或者figure('color',[1 1 1])或者set( ...

  2. 实现简单的django上传文件

    本文用django实现上传文件并保存到指定路径下,没有使用forms和models,步骤如下: 1.在模板中使用form表单,因为这个表单使用于上传文件的,所以method属性必须设置为post,而且 ...

  3. 如何判断Linux load的值是否过高

    1.先使用top看下CPU占用高的进程,找出进程的进程ID(pid): 查看方法:top 2.根据进程ID(pid)查看是进程的那些线程占用CPU高. 查看方法:top -Hp pid 3.使用pst ...

  4. Asp.net原理(第一篇)

    Asp.net (第一篇) 当用户在浏览器输入一个URL地址后,浏览器会发送一个请求到服务器.这时候在服务器上第一个负责处理请求的是IIS.然后IIS再根据请求的URL扩展名将请求分发给不同的ISAP ...

  5. Jquery:Jquery中的事件<二>

    这几天快忙死了,办了离职还得办入职,完全打乱了我的计划,但是能有一个理想的工作,还是很开心的,以后加把劲,争取把计划再赶上来!不说了,学习!!! 五.事件对象的属性 1.event.type:获取事件 ...

  6. Windows命令行(DOS命令)教程–2 (转载) http://arch.pconline.com.cn//pcedu/rookie/basic/10111/15325_1.html

    二.符号约定 为了便于说明格式,这里我们使用了一些符号约定,它们是通用的: C: 盘符 Path 路径 Filename 文件名 .ext 扩展名 Filespec 文件标识符 [ ] 方括号中的项目 ...

  7. iOS的Bundle资源束制作

    在静态库的制作中,很多时候我们的静态库也是带着文件,图片和多媒体资源的. 若只是直接加入到项目中也是可以,但是,考虑到方便管理(方便插件使用者的管理),我们希望把插件的资源文件打成一个包来管理. 当然 ...

  8. Struts2 技术全总结 (正在更新)

    背景:Struts1是一个高度成熟的框架,运行效率高,但其致命缺陷在于与JSP/Servlet的耦合非常紧密,因而导致了一些严重问题.其次,Struts1与Servlet API的严重耦合,使应用难以 ...

  9. 编译Boost 详细步骤

    vs2008编译boost [一.Boost库的介绍] Boost库是一个经过千锤百炼.可移植.提供源代码的C++库,作为标准库的后备,是C++标准化进程的发动机之一. Boost库由C++标准委员会 ...

  10. CString 字符串转化和分割

    1.格式化字符串 CString s;s.Format(_T("The num is %d."), i);相当于sprintf() 2.转为 int 转10进制最好用_ttoi() ...