程序员首页投稿(暂停使用,暂停投稿)

[Class文件结构]1——概述

2017-12-31  本文已影响184人  某昆

前言

众所周知,计算机只识别0和1,程序员写的c或c++程序最终都要经过编译、链接等步骤,将代码转换成0或1的二进制格式才能被计算机执行。由于依赖平台,导致c语音中基础数据类型在各平台上所占的字节数都不相同,其它细节也有不同。

Java不一样,Java在诞生之初就宣称“write once,run anywhere”,它选择了与操作系统和机器指令集无关的、平台中立的格式作为程序编译后的存储格式。

实现语言无关性的基础就是虚拟机和字节码存储格式,使用Java编译器可以把Java代码编译为存储字节码的Class文件,使用JRuby等的编译器一样可以把代码文件编译成Class文件,虚拟机并不关心Class文件的来源,只要它符合Class文件就有的结构就可以在Java虚拟机中运行。

Class类文件的结构

解析学习Class类文件的结构,是了解虚拟机的重要基础之一,有助于学习虚拟机如何加载并执行class文件。

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符。

Class文件采用一种类似C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表。

无符号数基于基本的数据类型,以u1,u2,u4,u8来分别代表1个字节、2个字节、4个字节、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或按照UTF-8编码构成的字符串值。

无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的集合。

注意:Class文件不像xml,它没有分隔符,而且它的顺序都是严格定义的。

Java代码中包含成员变量,方法,变量还定义了访问权限,方法还有参数、返回值,方法中的逻辑(比如说int型数据相加)是否会被存储成一个个指令,Class文件会如何描述这些东西呢?

示例代码

以如下代码为示例,查看编译后的Class文件。

package org.fenixsoft.clazz;

public class TestClass {

    private int m;

    public int inc(){
        return m + 1;
    }
}

Class文件是字节码文件,需要使用16进制编辑器查看,本文中选用 WinHex,打开Class文件后整体如下:

另外Class文件可以使用javap工具查看,指令如下:javap -verbose class文件路径

魔数与Class文件版本

每个Class文件的头4个字节称为魔数,它的唯一作用是用于确定这个文件是否为Class文件。查看上单节的Class文件格式图,魔数正好也是由一个u4类型表示。

Class文件的魔数为 cafebabe(咖啡宝贝?)。

魔数之后即是次版本号和主版本号,二者均由一个u2表示。

本例中次版本号为0,主版本号为51(33是16进制数,换成10进制就是51),即为Java 1.7。注意高版本的JDK能向下兼容,但无法运行以后版本的Class文件,那么本例中的Class文件只能被JDK 1.7及以上运行。

常量池

主版本号后边就是常量池了,常量池中的所有项目类型为:

使用javap -verbose 查看Class文件,查看常量池部分

常量池中存放两大类常量:字面量和符号引用。字面量,如字符串、被声明为final的常量值等。而符号引用(符号引用属于编译原理方面的概念)包括了下面三类常量:

和Java代码中的常量不一致,常量池中并不是只会存储final类型的数据,不是常量也会被存储,比如示例代码中的m,另外符号引用也并不是代码中所说的引用类型,除了类和接口,它还包含字段和方法。

虚拟机在加载Class文件时进行动态连接,也就是说Class文件并不会保存各个方法和字段的最终内存布局信息,当虚拟机运行时,需要从常量池中获取对应的符号引用,在运行时解析并翻译到具体的内存地址当中。

常量池是最繁琐的数据了,因为常量池中所包含的11种类型都有自己的结构。一般是由一位u1指定类型,再加上数据长度(一般是u2),再加上数据具体值。

上一篇下一篇

猜你喜欢

热点阅读