Java Class 字节码文件结构详解
Class字节码中有两种数据类型:
- 字节数据直接量:这是基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据。
- 表:表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的,它的结构体现在,组成表的成分所在的位置和顺序都是已经严格定义好的。
Class字节码总体结构如下:
具体详解请参考http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html

我在这里要说明几个细节问题:
- 为什么说常量表的数量是constant_pool_count-1,且索引从1开始而不是0。其实根本原因在于,索引为0也是一个常量(保留常量),只不过它不存在常量表,这个常量就对应null值。因此加上这个系统保留常量,常量个数共为constant_pool_count个,但是常量表数量要减1。
- 在常量池中,如果存在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 字节码文件结构详解的更多相关文章
- Kotlin伴生对象及其字节码内幕详解
继续面向对象,开撸就是!! 接口: 我们知道对于JDK8之后接口中除了方法的声明之后还可以有default方法的,而在Kotlin中也类似,下面来看一下在Kotlin接口相关的东东: 很显然就是一个方 ...
- 从 HelloWorld 看 Java 字节码文件结构
很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...
- Java之函数式接口@FunctionalInterface详解(附源码)
Java之函数式接口@FunctionalInterface详解 函数式接口的定义 在java8中,满足下面任意一个条件的接口都是函数式接口: 1.被@FunctionalInterface注释的接口 ...
- Java字节码文件结构剖析
今天起开启JVM的新的知识学习篇章----Java的字节码,那学习Java字节码有啥用呢?我们知道Java是跨平台的一门语言,编写一次到处运行,而支撑着这个特性的根基为两点:JVM和.class字节码 ...
- <JVM中篇:字节码与类的加载篇>01-Class字节码文件结构
笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...
- Java虚拟机之垃圾回收详解一
Java虚拟机之垃圾回收详解一 Java技术和JVM(Java虚拟机) 一.Java技术概述: Java是一门编程语言,是一种计算平台,是SUN公司于1995年首次发布.它是Java程序的技术基础,这 ...
- Java I/O : Java中的进制详解
作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层. ...
- Java网络编程和NIO详解开篇:Java网络编程基础
Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...
- Java网络编程和NIO详解8:浅析mmap和Direct Buffer
Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NI ...
随机推荐
- iOS键盘遮挡输入框,输入区域自动上移
在iOS开发过程当中,遇到关于键盘遮挡输入框的问题,经过网络参考与实践,总结如下: 登录窗口,上下放置两个UITextField,一个用户名,一个密码,放置的在屏幕下方1/3处,当点击用户名时,自动弹 ...
- Fiddler 抓取eclipse中的请求
Fiddler 抓取eclipse中的请求 代码中添加 System.setProperty("http.proxySet", "true"); System. ...
- mysql插入数据时,中文乱码
MySQL 插入数据时,中文乱码问题的解决(转) 当向 MySQL 数据库插入一条带有中文的数据形如 insert into employee values(null,'张三','female','1 ...
- css中的字体样式及元素样式
css中的字体样式一般包含有就9中,常见的有7种.这7种依次为: 1.字体样式:font-family: 2.字体大小:font-size: 3.字体加粗:font-weight: 4.字体斜体:fo ...
- 升级IOS9,提示滑动升级,卡在password锁屏界面,无反应了
注:升级之前一定要把锁屏password取消掉 若遇上述问题.可通过进入DFU 模式解决 进入DFU具体步骤.(进入成功后,屏幕为全黑) 注:在进入DFU操作时务必与电脑连接好数据线. 1.按住pow ...
- 五种常见的ASP.NET应用程序安全缺陷
下面给出了五个例子,阐述如何按照上述建议增强应用程序的安全性.这些例子示范了代码中可能出现的缺陷,以及它们带来的安全风险.如何改写最少的代码来有效地降低攻击风险.1 篡改参数◎ 使用ASP.NET域验 ...
- winscp连接vmware ubuntu
winscp连接vmware ubuntu,因为第一次安装在虚拟机上的ubuntu是没有ssh服务的,不能在windows上通过工具进行连接,所以需要以下步骤进行ssh配置 1:检查ssh状态 ...
- Parser Error Message: Access is denied【转】
PRB: Access Denied Error When You Make Code Modifications with Index Services Running View products ...
- hdu4497 正整数唯一分解定理应用
C - (例题)整数分解,计数 Crawling in process... Crawling failed Time Limit:1000MS Memory Limit:65535KB ...
- [每日一题] OCP1z0-047 :2013-07-30 表连接――内联视图当作表使用
用sys用户登录,给oe用户授权dba,以便可以用oe用户查看执行计划: oe@OCM> conn / as sysdba Connected. sys@OCM> grant dba to ...