技术标签: jvm jdk java # JVM 编程语言 class
一切的一切都是从javac开始的。从那一刻开始,java文件就从我们肉眼可分辨的文本文件,变成了冷冰冰的二进制文件。
变成了二进制文件是不是意味着我们无法再深入的去了解java class文件了呢?答案是否定的。
机器可以读,人为什么不能读?只要我们掌握java class文件的密码表,我们可以把二进制转成十六进制,将十六进制和我们的密码表进行对比,就可以轻松的解密了。
下面,让我们开始这个激动人心的过程吧。
为了深入理解java class的含义,我们首先需要定义一个class类:
public class JavaClassUsage {
private int age=18;
public void inc(int number){
this.age=this.age+ number;
}
}
很简单的类,我想不会有比它更简单的类了。
在上面的类中,我们定义了一个age字段和一个inc的方法。
接下来我们使用javac来进行编译。
IDEA有没有?直接打开编译后的class文件,你会看到什么?
没错,是反编译过来的java代码。但是这次我们需要深入了解的是class文件,于是我们可以选择 view->Show Bytecode:
当然,还是少不了最质朴的javap命令:
javap -verbose JavaClassUsage
对比会发现,其实javap展示的更清晰一些,我们暂时选用javap的结果。
编译的class文件有点长,我一度有点不想都列出来,但是又一想只有对才能讲述得更清楚,还是贴在下面:
public class com.flydean.JavaClassUsage
minor version: 0
major version: 58
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // com/flydean/JavaClassUsage.age:I
#8 = Class #10 // com/flydean/JavaClassUsage
#9 = NameAndType #11:#12 // age:I
#10 = Utf8 com/flydean/JavaClassUsage
#11 = Utf8 age
#12 = Utf8 I
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/flydean/JavaClassUsage;
#18 = Utf8 inc
#19 = Utf8 (I)V
#20 = Utf8 number
#21 = Utf8 SourceFile
#22 = Utf8 JavaClassUsage.java
{
public com.flydean.JavaClassUsage();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 18
7: putfield #7 // Field age:I
10: return
LineNumberTable:
line 7: 0
line 9: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/flydean/JavaClassUsage;
public void inc(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: aload_0
2: getfield #7 // Field age:I
5: iload_1
6: iadd
7: putfield #7 // Field age:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/flydean/JavaClassUsage;
0 11 1 number I
}
SourceFile: "JavaClassUsage.java"
慢着,上面javap的结果好像并不是二进制文件!
对的,javap是对二进制文件进行了解析,方便程序员阅读。如果你真的想直面最最底层的机器代码,就直接用支持16进制的文本编译器把编译好的class文件打开吧。
你准备好了吗?
来吧,展示吧!
上图左边是16进制的class文件代码,右边是对16进制文件的适当解析。大家可以隐约的看到一点点熟悉的内容。
是的,没错,你会读机器语言了!
如果你要了解class文件的结构,你需要这个密码本。
如果你想解析class文件,你需要这个密码本。
学好这个密码本,走遍天下都…没啥用!
下面就是密码本,也就是classFile的结构。
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中u2,u4表示的是无符号的两个字节,无符号的4个字节。
java class文件就是按照上面的格式排列下来的,按照这个格式,我们可以自己实现一个反编译器(大家有兴趣的话,可以自行研究)。
我们对比着上面的二进制文件一个一个的来理解。
首先,class文件的前4个字节叫做magic word。
看一下十六进制的第一行的前4个字节:
CA FE BA BE 00 00 00 3A 00 17 0A 00 02 00 03 07
0xCAFEBABE就是magic word。所有的java class文件都是以这4个字节开头的。
来一杯咖啡吧,baby!
多么有诗意的画面。
这两个version要连着讲,一个是主版本号,一个是次版本号。
00 00 00 3A
对比一下上面的表格,我们的主版本号是3A=58,也就是我们使用的是JDK14版本。
接下来是常量池。
首先是两个字节的constant_pool_count。对比一下,constant_pool_count的值是:
00 17
换算成十进制就是23。也就是说常量池的大小是23-1=22。
这里有两点要注意,第一点,常量池数组的index是从1开始到constant_pool_count-1结束。
第二点,常量池数组的第0位是作为一个保留位,表示“不引用任何常量池项目”,为某些特殊的情况下使用。
接下来是不定长度的cp_info:constant_pool[constant_pool_count-1]常量池数组。
常量池数组中存了些什么东西呢?
字符串常量,类和接口名字,字段名,和其他一些在class中引用的常量。
具体的constant_pool中存储的常量类型有下面几种:
每个常量都是以一个tag开头的。用来告诉JVM,这个到底是一个什么常量。
好了,我们对比着来看一下。在constant_pool_count之后,我们再取一部分16进制数据:
上面我们讲到了17是常量池的个数,接下来就是常量数组。
0A 00 02 00 03
首先第一个字节是常量的tag, 0A=10,对比一下上面的表格,10表示的是CONSTANT_Methodref方法引用。
CONSTANT_Methodref又是一个结构体,我们再看一下方法引用的定义:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
从上面的定义我们可以看出,CONSTANT_Methodref是由三部分组成的,第一部分是一个字节的tag,也就是上面的0A。
第二部分是2个字节的class_index,表示的是类在常量池中的index。
第三部分是2个字节的name_and_type_index,表示的是方法的名字和类型在常量池中的index。
先看class_index,0002=2。
常量池的第一个元素我们已经找到了就是CONSTANT_Methodref,第二个元素就是跟在CONSTANT_Methodref后面的部分,我们看下是什么:
07 00 04
一样的解析步骤,07=7,查表,表示的是CONSTANT_Class。
我们再看下CONSTANT_Class的定义:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
可以看到CONSTANT_Class占用3个字节,第一个字节是tag,后面两个字节是name在常量池中的索引。
00 04 = 4, 表示name在常量池中的索引是4。
然后我们就这样一路找下去,就得到了所有常量池中常量的信息。
这样找起来,眼睛都花了,有没有什么简单的办法呢?
当然有,就是上面的javap -version, 我们再回顾一下输出结果中的常量池部分:
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // com/flydean/JavaClassUsage.age:I
#8 = Class #10 // com/flydean/JavaClassUsage
#9 = NameAndType #11:#12 // age:I
#10 = Utf8 com/flydean/JavaClassUsage
#11 = Utf8 age
#12 = Utf8 I
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/flydean/JavaClassUsage;
#18 = Utf8 inc
#19 = Utf8 (I)V
#20 = Utf8 number
#21 = Utf8 SourceFile
#22 = Utf8 JavaClassUsage.java
以第一行为例,直接告诉你常量池中第一个index的类型是Methodref,它的classref是index=2,它的NameAndType是index=3。
并且直接在后面展示出了具体的值。
且慢,在常量池中我好像看到了一些不一样的东西,这些I,L是什么东西?
这些叫做字段描述符:
上图是他们的各项含义。除了8大基础类型,还有2个引用类型,分别是对象的实例,和数组。
常量池后面就是access_flags:访问描述符,表示的是这个class或者接口的访问权限。
先上密码表:
再找一下我们16进制的access_flag:
没错,就是00 21。 参照上面的表格,好像没有21,但是别怕:
21是ACC_PUBLIC和ACC_SUPER的并集。表示它有两个access权限。
接下来是this class和super class的名字,他们都是对常量池的引用。
00 08 00 02
this class的常量池index=8, super class的常量池index=2。
看一下2和8都代表什么:
#2 = Class #4 // java/lang/Object
#8 = Class #10 // com/flydean/JavaClassUsage
没错,JavaClassUsage的父类是Object。
大家知道为什么java只能单继承了吗?因为class文件里面只有一个u2的位置,放不下了!
接下来就是接口的数目和接口的具体信息数组了。
00 00
我们没有实现任何接口,所以interfaces_count=0,这时候也就没有interfaces[]了。
然后是字段数目和字段具体的数组信息。
这里的字段包括类变量和实例变量。
每个字段信息也是一个结构体:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
字段的access_flag跟class的有点不一样:
这里我们就不具体对比解释了,感兴趣的小伙伴可以自行体验。
接下来是方法信息。
method结构体:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
method访问权限标记:
attributes被用在ClassFile, field_info, method_info和Code_attribute这些结构体中。
先看下attributes结构体的定义:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
都有哪些attributes, 这些attributes都用在什么地方呢?
其中有六个属性对于Java虚拟机正确解释类文件至关重要,他们是:
ConstantValue,Code,StackMapTable,BootstrapMethods,NestHost和NestMembers。
九个属性对于Java虚拟机正确解释类文件不是至关重要的,但是对于通过Java SE Platform的类库正确解释类文件是至关重要的,他们是:
Exceptions,InnerClasses,EnclosingMethod,Synthetic,Signature,SourceFile,LineNumberTable,LocalVariableTable,LocalVariableTypeTable。
其他13个属性,不是那么重要,但是包含有关类文件的元数据。
最后留给大家一个问题,java class中常量池的大小constant_pool_count是2个字节,两个字节可以表示2的16次方个常量。很明显已经够大了。
但是,万一我们写了超过2个字节大小的常量怎么办?欢迎大家留言给我讨论。
本文链接:http://www.flydean.com/jvm-class-file-structure/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
文章浏览阅读1.5w次,点赞9次,收藏26次。简介一个创建某个文件到桌面快捷方式的BAT批处理.代码@echooff::设置程序或文件的完整路径(必选)setProgram=D:\Program Files (x86)\格式工厂.4.2.0\FormatFactory.exe::设置快捷方式名称(必选)setLnkName=格式工厂v4.2.0::设置程序的工作路径,一般为程序主目录,此项若留空,脚本将..._批处理创建桌面快捷方式
文章浏览阅读2k次。射频识别技术漫谈(6-10),概述RFID的通讯协议;射频ID卡的原理与实现,数据的传输与解码;介绍动物标签属性与数据传输;RFID识别号的变化等_芯片 ttf模式
文章浏览阅读1.1k次。今天小编心血来潮,为大家带来一个很有趣的项目,那就是使用 Python web 框架 Django 来实现支付宝支付,废话不多说,一起来看看如何实现吧。_django 对接支付宝接口流程
文章浏览阅读842次。Zabbix 5.0 LTS,跑了一年多了一直很稳定,前两天空间显示快满了,于是手贱清理了一下history_uint表(使用mysql truncate),结果折腾了一周。大概故障如下:然后zabbix论坛、各种群问了好久都没解决,最后自己一番折腾似乎搞定了。初步怀疑,应该是由于历史数据被清空后,zabbix需要去处理数据,但是数据量太大,跑不过来,所以来不及更新了(?)..._zabbix问题没有更新
文章浏览阅读296次。一、数据结构之字典 key-value
文章浏览阅读9.7k次,点赞3次,收藏13次。最近项目中遇到一个问题,是关于mybatis-plus的字段注解策略,记录一下。1问题调用了A组件(基础组件),来更新自身组件的数据,发现自己组件有个字段总是被清空。2原因分析调用的A组件的字段,属于基础字段,自己业务组件,对这个基础字段做了扩展,增加了业务字段。但是在自己的组件中的实体注解上,有一个注解使用错误。mybatis-plus封装的updateById方法,如果..._mybatisplus strategy
文章浏览阅读3.8k次。如何构建倒排索引,我们将这个过程叫做“索引构建”。如果我们的文档很多,这样索引就一次性装不下内存,该如何构建。硬件的限制 我们知道ram读写是随机的操作,只要输入相应的地址单元就能瞬间将数据读出来或者写进去。但是磁盘不行,磁盘必须有一个寻道的过程,外加一个旋转时间。那么只有涉及到磁盘,我们就可以考虑怎么节省I/O操作时间。【注】操作系统往往以数据块为单位进行读写。因为读一_为某一文档及集构件词项索引时,可使用哪些索引构建方法
文章浏览阅读836次。英特尔技术与制造事业部副总裁卞成刚7日在财富论坛间隙接受中新社记者采访时表示,该公司看好中国市场前景,扎根中国并以此走向世界是目前最重要的战略之一。卞成刚说,目前该公司正面临战略转型,即从传统PC服务领域扩展至所有智能设施领域,特别是移动终端。而中国目前正引领全球手机市场,预计未来手机、平板电脑等方面的发明创新将大量在中国市场涌现,并推向全球。持相同态度的还有英特尔中国区执行董事戈峻。戈峻
文章浏览阅读627次。https://blog.csdn.net/zrs19800702/article/details/53101213http://blog.csdn.net/lzw06061139/article/details/51445311https://my.oschina.net/linuxhunter/blog/654080rgw 概述Ceph 通过radosgw提供RES..._radosgw -c
文章浏览阅读3.7k次,点赞6次,收藏9次。我为什么选择ECharts ? 本周学校课程设计,原本随机佛系选了一个51单片机来做音乐播放器,结果在粗略玩了CN-DBpedia两天后才回过神,课设还没有开始整。于是懒癌发作,碍于身上还有比赛的作品没交,本菜鸡对硬件也没啥天赋,所以就直接把题目切换成软件方面的题目。写python的同学选择了一个时间序列数据的可视化曲线程序设计题目,果真python在数据可视化这一点性能很优秀。..._echarts 时间序列
文章浏览阅读1.6k次。事件类:/** * * * @className: EarlyWarnPublishEvent * * @description:数据风险预警发布事件 * * @param: * * @return: * * @throws: * * @author: lizz * * @date: 2020/05/06 15:31 * */public cl..._applicationeventpublisheraware
文章浏览阅读1.2k次。如需转载请注明出处!点击小图片转到图片查看的页面在Android开发中很常用到,抱着学习和分享的心态,在这里写下自己自定义的一个ImageView,可以实现类似微信朋友圈中查看图片的功能和效果。主要功能需求:1.缩放限制:自由缩放,有最大和最小的缩放限制 2居中显示:.若图片没充满整个ImageView,则缩放过程将图片居中 3.双击缩放:根据当前缩放的状态,双击放大两倍或缩小到原来 4.单指_imageview图片边界回弹