在上一篇OpenJDK源码研究笔记(七)–Java字码文件(.class)的结构中,我们大致了解了Java字节码文件的结构。

本篇详细地介绍了如何读取.class文件的大部分细节。

1.构造文件 

 // 字节码文件User.class
String userClass = "C:/User.class";
File file = new File(userClass);

2.构造输入流

FileInputStream fin = new FileInputStream(file);

DataInputStream in= new DataInputStream(new BufferedInputStream(fin));

3.读取字节码文件(.class)的内容

3.1魔法数

//无符号4个字节
int magic = in.readInt();

   3.2次版本号

//无符号2个字节
     int   minor_version = in.readUnsignedShort();

3.3主版本号

 //无符号2个字节
int major_version = in.readUnsignedShort();

3.4常量池的个数
      //无符号2个字节
       int count = cr.readUnsignedShort();

3.5常量池的内容

//CPInfo是常量池中的常量的类型,不能简单地把常量池中的类型理解为String类型

   CPInfo[]   pool = new CPInfo[count];
for (int i = 1; i < count; i++) {
// 常量池中常量的类型标记
int tag = cr.readUnsignedByte();
switch (tag) {
// 类或接口的符号引用
case CONSTANT_Class:
pool[i] = new CONSTANT_Class_info(this, cr);
break;
// 双精度浮点型字面量
case CONSTANT_Double:
pool[i] = new CONSTANT_Double_info(cr);
i++;
break;
// 字段的符号引用
case CONSTANT_Fieldref:
pool[i] = new CONSTANT_Fieldref_info(this, cr);
break;
// 浮点型字面量
case CONSTANT_Float:
pool[i] = new CONSTANT_Float_info(cr);
break;
// 整型字面量
case CONSTANT_Integer:
pool[i] = new CONSTANT_Integer_info(cr);
break;
// 接口方法的引用
case CONSTANT_InterfaceMethodref:
pool[i] = new CONSTANT_InterfaceMethodref_info(this, cr);
break;
// 动态调用指令
case CONSTANT_InvokeDynamic:
pool[i] = new CONSTANT_InvokeDynamic_info(this, cr);
break;
// 长整型字面量
case CONSTANT_Long:
pool[i] = new CONSTANT_Long_info(cr);
i++;
break;
// 方法处理器
case CONSTANT_MethodHandle:
pool[i] = new CONSTANT_MethodHandle_info(this, cr);
break;
// 方法类型
case CONSTANT_MethodType:
pool[i] = new CONSTANT_MethodType_info(this, cr);
break;
// 方法引用
case CONSTANT_Methodref:
pool[i] = new CONSTANT_Methodref_info(this, cr);
break;
// 名字和类型
case CONSTANT_NameAndType:
pool[i] = new CONSTANT_NameAndType_info(this, cr);
break;
// 字符串
case CONSTANT_String:
pool[i] = new CONSTANT_String_info(this, cr);
break;
// Utf8编码的字符串
case CONSTANT_Utf8:
pool[i] = new CONSTANT_Utf8_info(cr);
break;
// 不合法的类型
default:
throw new InvalidEntry(i, tag);
}
}

构造细节

举一个例子,比如构造Double类型的常量结构CONSTANT_Double_info,就是在这个结构中保存一个Double的值。

此外,还维护了一些其它信息,这个结构的字节长度、类型标记。

其它一些结构,类似。

3.6访问标识符

 //无符号2个字节

    int access_flag= cr.readUnsignedShort()

3.7这个类在常量池中的下标

//无符号2个字节
int this_class = in.readUnsignedShort();

3.8这个类的父类在常量池中的下标

  //无符号2个字节
int super_class = in.readUnsignedShort();

   3.9父接口的数量

//无符号2个字节

int   int interfaces_count = cr.readUnsignedShort();

3.10父接口的内容

interfaces = new int[interfaces_count];

     for (int i = 0; i < interfaces_count; i++){

           //父接口的类型,无符号2个字节
interfaces[i] = cr.readUnsignedShort(); }

   3.11字段的数量

//无符号2个字节

    int    int fields_count = cr.readUnsignedShort();

3.12字段的内容

fields = new Field[fields_count];
for (int i = 0; i < fields_count; i++){
fields[i] = new Field(cr);
} //一个字段Field有访问标识符、名字、描述符、属性 access_flags = cr.readUnsignedShort();
name_index = cr.readUnsignedShort();
descriptor = cr.readUnsignedShort() attributes,封装了字段的更多详细信息
 3.13方法的数量,无符号2个字节
 int methods_count = cr.readUnsignedShort();

3.14方法的内容

 methods = new Method[methods_count];

   for (int i = 0; i < methods_count; i++)
methods[i] = new Method(cr); // 一个方法Method有访问标识符、名字、描述符、属性 access_flags = cr.readUnsignedShort();
name_index = cr.readUnsignedShort();
descriptor = cr.readUnsignedShort() attributes,封装了方法的更多详细信息

       3.15属性的数量

 //无符号2个字节

        int attrs_count = cr.readUnsignedShort();

3.16属性的内容

Attribute[]  attrs = new Attribute[attrs_count];
for (int i = 0; i < attrs_count; i++) {
attrs[i] = readAttribute(); } /**
* 读取一个属性Attribute
*/
public Attribute readAttribute() throws IOException {
//名字的索引
int name_index = readUnsignedShort();
//属性内容的长度
int length = readInt();
byte[] data = new byte[length];
//读取length个长度的字节信息到data中
readFully(data); DataInputStream prev = in;
in = new DataInputStream(new ByteArrayInputStream(data));
try {
//根据名字的索引、字节数组形式的内容构造一个Attribute对象
return attributeFactory.createAttribute(this, name_index, data);
} finally {
in = prev;
}
}

4.总结

一个字节码文件(.class)几乎含有一个类或接口的所有信息。

这些信息虽然以二进制形式存在,但是它们的存储结构仍然是有规律的。

按照存储格式来依次读取相应的字节数目,就可以完整地解析一个字节码文件。

5.挑战极限

讲完了字节码文件的的结构和读取字节码文件的方法,有读者可能会问,字节码文件是怎么生成的?

我们写的Java程序都是文本格式的.java文件,它们是如何转换的呢?

其实,.java到.class的转换过程,就是Java编译器javac的主要功能。

Java编译器javac比较有难度,刚刚开始研究,如有心得体会和研究成功,一定第一时间分享出来。

6.相关阅读

OpenJDK源码研究笔记(一)-参数检查&抛出带关键错误提示信息的异常

OpenJDK源码研究笔记(二)-Comparable和Comparator2个接口的作用和区别(一道经典的Java笔试面试题)

OpenJDK源码研究笔记(三)-RandomAccess等标记接口的作用

OpenJDK源码研究笔记(四)-编写和组织可复用的工具类和方法

OpenJDK源码研究笔记(五)-缓存Integer等类型的频繁使用的数据和对象,大幅度提升性能(一道经典的Java笔试题)

OpenJDK源码研究笔记(六)--观察者模式工具类(Observer和Observable)和应用示例

OpenJDK源码研究笔记(七)–Java字节码文件(.class)的结构

原文参见http://FansUnion.cn/articles/2974

OpenJDK源码研究笔记(八)-详细解析如何读取Java字节码文件(.class)的更多相关文章

  1. OpenJDK源码研究笔记(二)-Comparable和Comparator2个接口的作用和区别(一道经典的Java笔试面试题)

    Comparable和Comparator是JDK中定义的2个比较接口,很相似,但又有所不同. 这2个接口的作用和区别也是Java中的常见经典面试题. 下面我们就来详细介绍下这2个接口的定义.作用.区 ...

  2. OpenJDK源码研究笔记(七)–Java字节码文件(.class)的结构

    最近在看OpenJDK源码的过程中,顺便看了Java编译器(javac)的源码. 为了理解javac的源码,需要先搞懂Java字节码文件(.class)的结构. 于是,我就认真看了下OpenJDK中J ...

  3. OpenJDK源码研究笔记(十五):吐槽JDK中的10个富有争议的设计

    前14篇文章,分享了JDK中值得学习和借鉴的编码和设计方法. 每个硬币都是有两面的.Every coin has two sides. 当然,JDK中也有很多值得改进或者说富有争议的设计. 本篇,就来 ...

  4. OpenJDK源码研究笔记(十三):Javac编译过程中的上下文容器(Context)、单例(Singleton)和延迟创建(LazyCreation)3种模式

    在阅读Javac源码的过程中,发现一个上下文对象Context. 这个对象用来确保一次编译过程中的用到的类都只有一个实例,即实现我们经常提到的"单例模式". 今天,特意对这个上下文 ...

  5. [置顶] OpenJDK源码研究笔记(九)-可恨却又可亲的的异常(NullPointerException)

    可恨的异常 程序开发过程中,最讨厌异常了. 异常代表着程序出了问题,一旦出现,控制台会出现一屏又一屏的堆栈错误信息. 看着就让人心烦. 对于一个新人来讲,遇到异常经常会压力大,手忙脚乱,心生畏惧. 可 ...

  6. OpenJDK源码研究笔记(五)-缓存Integer等类型的频繁使用的数据和对象,大幅度提升性能(一道经典的Java笔试题)

    摘要 本文先给出一个看似很简单实则有深意的Java笔试面试题,引出JDK内部的缓存. JDK内部的缓存,主要是为了提高Java程序的性能. 你能答对这道"看似简单,实则有深意"的J ...

  7. OpenJDK源码研究笔记(四)-编写和组织可复用的工具类和方法

    本篇主要讲解java.util.Arrays这个针对数组的工具类. 1.可复用的工具类和方法.  这个工具类里,包含很多针对数组的工具方法,如 排序.交换.二分查找.比较.填充.复制.hashcode ...

  8. OpenJDK源码研究笔记(一)-参数检查&抛出带关键错误提示信息的异常

    OpenJDK源码研究笔记系列文章,是我在阅读OpenJDK7源码的过程中的一些体会.收获.看法. 把研究过程中的成长和收获一点点地整理出来,是对自己研究学习的一个小结,也有可能给学习Java的一些同 ...

  9. OpenJDK源码研究笔记(九)-可恨却又可亲的的异常(NullPointerException)

    可恨的异常 程序开发过程中,最讨厌异常了. 异常代表着程序出了问题,一旦出现,控制台会出现一屏又一屏的堆栈错误信息. 看着就让人心烦. 对于一个新人来讲,遇到异常经常会压力大,手忙脚乱,心生畏惧. 可 ...

随机推荐

  1. Android 度量单位

    单位 注释 px(像素) 每个像素对应手机上的一个点,在不同设备上1px表示的长度不一定相同 screen size(屏幕尺寸) 指手机对角线的长度,如4.7英寸 resolution(分辨率) 指屏 ...

  2. win10+ubuntu的坑

    最近几天考虑了诸多,包括分区大小,使用烧写工具等等. 但是实际动手还是遇到了彩蛋.rufus是知乎的大神推荐的,因为UUI貌似有些版本安装时候有些问题. 而rufus的界面有诸多选项.ubuntu的图 ...

  3. HDU 2852 KiKi's K-Number【 树状数组 二分 】

    题意:给出m个操作,0:是增加一个数,add(x,1)1:是删除一个指定的数,这个是看sum(x) - sum(x-1)是否为0,为0的话则不存在,不为0的话,则add(x,-1)2:是查询比x大的数 ...

  4. ACM-ICPC 2018 徐州赛区网络预赛 H. Ryuji doesn't want to study(树状数组)

    Output For each question, output one line with one integer represent the answer. 样例输入 5 3 1 2 3 4 5 ...

  5. Python内置数据结构之列表list

    1. Python的数据类型简介 数据结构是以某种方式(如通过编号)组合起来的数据元素(如数.字符乃至其他数据结构)集合.在Python中,最基本的数据结构为序列(sequence). Python内 ...

  6. 结构化数据、半结构化数据、非结构化数据——Hadoop处理非结构化数据

    刚开始接触Hadoop ,指南中说Hadoop处理非结构化数据,学习数据库的时候,老师总提结构化数据,就是一张二维表,那非结构化数据是什么呢?难道是文本那样的文件?经过上网搜索,感觉这个帖子不错 网址 ...

  7. mven系列问题

    1.前言 Maven,发音是[`meivin],"专家"的意思.它是一个很好的项目管理工具,很早就进入了我的必备工具行列,但是这次为了把 project1项目完全迁移并应用mave ...

  8. 关于Jwt的一些思考

    在使用jwt的过程中发现了两个问题续期和退出的问题. 续期 因为jwt的token在签发之后是有过期时间的,所以就存在管理这个过期时间的问题.我看网上有提出解决方案的大致有下面几个 每次更新过期时间, ...

  9. Linux内存管理与C存储空间

    ELF文件 在学习之前我们先看看ELF文件. ELF分为三种类型:.o 可重定位文件(relocalble file),可执行文件以及共享库(shared library),三种格式基本上从结构上是一 ...

  10. max带来的冲突

    题目要求: /* * Copyright (c) 2014, 烟台大学计算机学院 * All rights reserved. * 文件名:sum123.cpp * 作 者:林海云 * 完毕日期:20 ...