https://www.jianshu.com/p/2c106b682cfb
java程序最终是要转换成class文件执行在虚拟机上的,那么class文件是个怎样的结构,虚拟机又是如何处理去执行class文件里面的内容。
1.Demo源码
首先,编写一个简单的java源码:
package com.taotao.test;
/**
* @author aping
* @time 2020/8/6 13:46
*/
public class Demo {
private static int m = 1;
public static int add() {
m = m + 1;
return m;
}
public static void main(String[] args) {
add();
}
}
这段代码很简单,只有一个成员变量m 和一个方法 add()。
2,字节码
要运行一段java源码,必须先将源码转换为class文件,class文件就是编译器编译之后供虚拟机解释执行的二进制字节码文件。运行:生成一个Demo.class文件。
// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available
package com.taotao.test;
public class Demo {
private static int m;
public Demo() { /* compiled code */ }
public static int add() { /* compiled code */ }
public static void main(java.lang.String[] args) { /* compiled code */ }
}
我们打开Demo.class 文件看下。这里用到的是Notepad++,需要安装一个HEX-Editor插件
image.png
在分析class文件之前,我们先来将这个Demo.class反编译回Demo.java的结果,如下图所示:
image.png
源码
image.pngclass转java
可以看到,回编译的源码比编写的代码多了一个空的构造函数和this关键字,为什么呢?先放下这个疑问,看看这个分析
4,字节码结构:
从上面的字节码文件中我们可以看到,里面就是一堆的16进制字节。那么该如何解读呢?我们先看这个表》
这是一张java字节码总的结构表,我们按照上面的顺序逐一进行解读就可以了。
首先说明下:class文件只有俩种数据类型: 无符号表和表。如下表所示:
image.png
实际上整个class文件就是一张表,其结构就是上面的表一了。
那么我们看下表一中的类型那一列也就简单了
上面各种具体的表的数据结构后面会详细说明,这里暂且不表。
好了,我们开始对那一堆的16进制进行解读。
4.1魔数
从上面的总的结构图中可以看到,开头的4个字节表示的是魔数,其值为:
image.png魔数就是用来区分文件类型的一种标志,一般都是用文件的前几个字节来表示.比如: 0XCAFE BABE表示的是class文件,那么为什么不是用文件名后缀来进行判断呢?因为文件名后缀容易被修改,所以为了保证文件的安全性,将文件类型写在内部可以保证不被篡改。、
再来说说 为什么class文件用的是CAFE BABE呢,看看这个就懂了。
4.2版本号
紧跟着魔数后面的4位就是版本号,同样也是4个字节,同时前2个字节表示副版本号,后2个字节表示主版本号。再来看看我们Demo字节码中的值:
前面两个字节是0x0000,也就是其值为0;
后面两个字节是0x0034,也就是其值为52.
所以上面的代码就是52.0版本来编译的,也就是jdk1.8.0。
4.3常量池
4.3.1 常量池容量计数器
接下来就是常量池了,由于常量池的数量不固定,时长时短。所以需要放置2个字节来表示常量池容量计数值。Demo的值为:
image.png
其值为001e
通常我们写代码时都是从0开始的,但是这里的常量池却是从1开始,因为他把第0项常量空出来了。这是为了在满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可用索引值0来表示。
Class文件中只有常量池的容量计数是从1开始的,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。
4.3.2 字面量和符号引用
对这些常量解读前,我们需要搞清楚几个概念。
常量池主要存放2大类常量: 字面量和符号引用。如下表:
image.png
4.3.2.1 全限定名
com/april/test/Demo这个就是类的全限定名,仅仅是把包名的"."替换成"/",为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“;”表示全限定名结束。
4.3.2.2 简单名称
简单名称是指没有类型和参数修饰的方法或者字段名称,上面例子中的类的add()方法和num字段的简单名称分别是add和num。
4.3.2.3 描述符
描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,详见下表:
用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“( )”之内。如方法java.lang.String toString()的描述符为( ) LJava/lang/String;,方法int abc(int[] x, int y)的描述符为([II) I。
image.png image.png注:此表格的类型的单位不对,不是bit,应该是byte(字节)。后面的同理。
4.3.4
常量解读:
好了,我们进入这18项常量的解读,首先是第一个常量,看下他的标志位是啥:
image.png
其值为0x0a,即10,查上面的表格可知,其对应的项目类型为CONSTANT_Methodref_info,即类中方法的符号引用。其结构为:
image.png
即后面4个字节都是他的内容,分别为2个索引项:
image.png4.4 访问标志
常量池后面就是访问标志。用2个字节来表示,其标识了类或者接口的访问信息,比如: 该class
网友评论