Java 进阶:实例详解 Java 虚拟机字节码指令(一)

毕业于 C9 高校,硕士学历,曾在 IEEE ITS、VSD 等 Top 期刊发表论文。多年研发经验,精通 Java、Python 及 C 语言,擅长预测算法,分布式中间件;曾在华为、阿里巴巴,上海电气等公司重要项目中担任技术负责人或核心研发成员,现专注于中间件技术,同时长期负责招聘。

文章正文

众所周知,Java 语言编译生成的 class 文件可以运行在任何支持 Java 虚拟机的硬件平台和操作系统上。那么,你是否思考过:class 文件包含哪些内容?是如何在虚拟机中执行的?要弄清楚这些问题,须了解 class 文件结构和 Java 虚拟机字节码指令。Java 虚拟机字节码指令非常重要,学习它对深入理解虚拟机、栈、锁、异常、同步等的原理十分有益,是 Java 进阶之路必读内容之一。

本系列一共有两篇文章,本场 Chat 为第一篇,主要内容如下:

  • 图文解读 class 文件结构;
  • Java 字节码介绍;
  • 详解 Java 虚拟机栈结构;
  • 实例解读 Java 虚拟机中变量、常量访问原理和指令体系;
  • 实例解读 Java 虚拟机中对象、数组(数值数组、对象数组、多维数组)创建指令;
  • 实例解读 Java 虚拟机中方法、字段 (又称域-field) 访问的原理及指令体系。

引言

计算机本身只能识别 0 和 1 构成的机器码,因此,任何编程语言最终都需要编译成机器码才能被计算机执行。以 C/C++ 为例,用它们编写的程序首先被编译,然后被连接成单独的、支持特定硬件平台和操作系统的二进制文件。通常情况下,一个平台上的二进制可执行文件不能在其它平台上工作。

Java 在诞生之初便提出了著名的 slogan:“Write Once, Run Anywhere”,为了实现这一目标,Java 虚拟机应运而生。目前,Java 虚拟机有很多版本,但它们都具有一个共同的特征——可以载入并执行同一种与平台无关的字节码(ByteCode)。 因为 Java 虚拟机的出现,Java 源代码不必根据不同平台编译成 0 和 1,而是间接翻译成字节码,储存字节码的文件再交由运行于不同平台上的 Java 虚拟机去读取执行,从而实现一次编写,到处运行的目的。

Java 编译生成的字节码文件为 ".class " 文件,它是一种二进制文件,其中包含了 Java 虚拟机指令集和符号表以及若干其它辅助信息。作为一个通用的、机器无关的执行平台,任何其它语言的实现者都可以将 Java 虚拟机作为语言的产品交付媒介。例如,使用 Java 编译器可以把 Java 代码编译成存储字节码的 class 文件,使用 Groovy、 Scala、 Koltin 等其它语言的编译器一样可以把程序代码编译成 class 文件,虚拟机并不关心 class 的来源是何种语言。

Class 文件结构

class 文件是一组以 8 位字节位基础单位的二进制流,采用一种类似 C 语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表。无符号数属于基本的数据类型,以 u1、u2、u4、u8 分别代表1个字节、2个字节、4个字节、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或 utf-8 编码构成的字符串值。表是由多个无符号数或其它表作为数据项构成的复合数据类型,所有表都习惯性地以 _info 结尾。 每一个 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;              //类索引,表示这个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];    //属性表
}

更直观地,通过图片来展示 class 文件的结构,如下所示:

在这里插入图片描述

Class 文件结构实例

从上面的介绍 class 文件的结构比较复杂,事实上,我们可以将 class 文件分为以下部分。

class文件: 文件描述、常量池、类概述、字段表、方法表、扩展信息表。为了便于读者理解,在此,我们先来看一个实例,Java 源码如下:

public class Test {
    private String  attribute_1;
    private Integer attribute_2;

    public void testMethod_1() {
    }
    public String testMethod_2(String param) {
        return param;
    }
}

通过命令“javac Test.java”编译后,可以得到 Test.class 文件,这就是所谓的字节码文件,Test.class 内容如下所示:

cafe babe 0000 0032 0018 0700 0201 001e 636f 6d2f 7465 7374 2f64 6f63 2f65 7870 2f54 6573
7443 6c61 7373 436f 6465 0700 0401 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 0b61
7474 7269 6275 7465 5f31 0100 124c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b01 000b 6174 
7472 6962 7574 655f 3201 0013 4c6a 6176 612f 6c61 6e67 2f49 6e74 6567 6572 3b01 0006 3c69 
6e69 743e 0100 0328 2956 0100 0443 6f64 650a 0003 000d 0c00 0900 0a01 000f 4c69 6e65 4e75 
6d62 6572 5461 626c 6501 0012 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65 0100 0474 6869 
7301 0020 4c63 6f6d 2f74 6573 742f 646f 632f 6578 702f 5465 7374 436c 6173 7343 6f64 653b 
0100 0f74 6573 7449 6e74 6572 6661 6365 5f31 0100 0f74 6573 7449 6e74 6572 6661 6365 5f32 
0100 2628 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 294c 6a61 7661 2f6c 616e 672f 5374 
7269 6e67 3b01 0005 7061 7261 6d01 000a 536f 7572 6365 4669 6c65 0100 1254 6573 7443 6c61 
7373 436f 6465 2e6a 6176 6100 2100 0100 0300 0000 0200 0200 0500 0600 0000 0400 0700 0800 
0000 0300 0100 0900 0a00 0100 0b00 0000 2f00 0100 0100 0000 052a b700 0cb1 0000 0002 000e 
0000 0006 0001 0000 0003 000f 0000 000c 0001 0000 0005 0010 0011 0000 0001 0012 000a 0001 
000b 0000 002b 0000 0001 0000 0001 b100 0000 0200 0e00 0000 0600 0100 0000 0b00 0f00 0000 
0c00 0100 0000 0100 1000 1100 0000 0100 1300 1400 0100 0b00 0000 3600 0100 0200 0000 022b 
b000 0000 0200 0e00 0000 0600 0100 0000 0e00 0f00 0000 1600 0200 0000 0200 1000 1100 0000 
0000 0200 1500 0600 0100 0100 1600 0000 0200 17

原生的字节码文件不便于人阅读 (奇人异士除外,听说有人可以口算 MD5 值),我们可以通过命令“javap –verbose Test.class”,更直观地查看内容:

Classfile /Users/admin/Code/Test.class
  Last modified 2019-7-14; size 421 bytes
  MD5 checksum 643ae9b4f59aab002dab9bef873ffcbb
  Compiled from "Test1.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#17         // java/lang/Object."<init>":()V
   #
                        
作者正在撰写中...
隐藏内容 支付可见
内容互动
写评论
加载更多
评论文章
¥6.66 购买
× 订阅 Java 精选频道
¥ 元/月
订阅即可免费阅读所有精选内容