JVM 内部原理(三)— 基本概念之类文件格式

介绍

版本:Java SE 7

每位使用 Java 的程序员都知道 Java 字节码在 Java 运行时(JRE - Java Runtime Environment)里运行。Java 虚拟机(JVM - Java Virtual Machine)是 Java 运行时(JRE)的重要组成部分,它可以分析和执行 Java 字节码。Java 程序员不需要知道 JVM 是如何工作的。有很多应用程序和应用程序库都已开发完成,但是它们并不需要开发者对 JVM 有深入的理解。但是,如果你理解 JVM ,那么就可以对 Java 更有了解,这也使得那些看似简单而又难以解决的问题得以解决。

在本篇文章中,我会解释 JVM 是如何工作的,它的结构如何,字节码是如何执行的及其执行顺序,与一些常见的错误及其解决方案,还有 Java 7 的新特性。

目录

  • 虚拟机(Virtual Machine)

  • Java 字节码

    • 症状

    • 原因

  • 类文件格式(Class File Format)

    • 症状

    • 原因

  • JVM 结构

    • 类装载器(Class Loader)

    • 运行时数据区

    • 执行引擎

  • Java 虚拟机官方规范文档,第 7 版

    • 分支语句中的字符串
  • 总结

内容

类文件格式(Class File Format)

在解释 Java 类文件格式之前,让我们先查看一个 Java Web 应用程序经常出现的状况。

症状

当在 Tomcat 上运行 JSP 时,JSP 并没有运行,而是出现一下错误。

1 Servlet.service() for servlet jsp threw exception org.apache.jasper.JasperException: Unable to compile class for JSP Generated servlet error:
2 The code of method _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit"

原因

以上的错误消息提示会因为 Web 应用服务器不同而有些许差异,但有有样事情是一样的:那就是 65535 字节的限制。65535 字节的限制是由于 JVM 的限制规定,那就是 一个方法的大小不可以超过 65535 字节

我会解释 65535 字节的限制以及为什么会有这样的限制。

Java 字节码使用的 branch/jump 指令是 “goto” 和 “jsr” 。

1 goto [branchbyte1] [branchbyte2]
2 jsr [branchbyte1] [branchbyte2]

两者都接收一个 2 字节有符号的 branch 偏移量作为他们的操作数,这样它可以扩展到最大 65535 索引。然而,为了支持足够的 branch ,Java 字节码提供了 “goto_w” 和 “jsr_w” 接收 4 字节有符号的 branch 偏移量。

1 goto_w [branchbyte1] [branchbyte2] [branchbyte3] [branchbyte4]
2 jsr_w [branchbyte1] [branchbyte2] [branchbyte3] [branchbyte4]

有了这两条指令,branch 可以提供超过 65535 的地址索引,这样就可以解决对于 Java 方法 65535 字节的大小的限制。然而,由于 Java 类文件格式其他诸多方面的限制,Java 方法仍然无法超过 65535 字节。为了解释其他的这些限制,我会通过类文件格式来进行简单的说明。

一个 Java 类文件结构如下:

 1 ClassFile {
2 u4 magic;
3 u2 minor_version;
4 u2 major_version;
5 u2 constant_pool_count;
6 cp_info constant_pool[constant_pool_count-1];
7 u2 access_flags;
8 u2 this_class;
9 u2 super_class;
10 u2 interfaces_count;
11 u2 interfaces[interfaces_count];
12 u2 fields_count;
13 field_info fields[fields_count];
14 u2 methods_count;
15 method_info methods[methods_count];
16 u2 attributes_count;
17 attribute_info attributes[attributes_count];}

首 16 字节 UserService.class 文件反编译后的十六进制显示如下:

ca fe ba be 00 00 00 32 00 28 07 00 02 01 00 1b

将这个值与类文件格式一起查看。

  • magic:类文件的前 4 字节的内容是魔数(magic number)。它的值已经预先指定好,用来区分 Java 类文件。在以上十六进制中,它的值始终是 0xCAFEBABE 。简而言之,当文件的前 4 字节是 0xCAFEBABE 时,那么它就是这个 Java 类文件。

  • minor_version,major_version:后面接着的 4 字节表示类的版本。UserService.class 文件这个值是 0x00000032 ,类的版本是 50.0 。由 JDK1.6 编译的类文件版本是 50.0 ,有 JDK1.5 编译的类文件版本是 49.0 。JVM 必须对比它低版本编译的类文件保持向后兼容。另一方面,当更高版本的类文件在低版本的 JVM 中执行是,java.lang.UnsupportedClassVersionError 就会出现。

  • constant_pool_count,constant_pool[]:在版本信息之后,是类的类型常量池信息。它的信息包括了运行时常量池区域,我们稍后对此进行解释。当装载类文件时,JVM 将常量池(constant_pool)的信息保存在方法区(method area)的运行时常量池区(Runtime Constant Pool area)。因为类文件 UserService.class 的 constant_pool_count 值为 0x0028 ,那么 constant_pool 有(40-1)个索引,即 39 个索引。

  • access_flags:这个标志表示类的修饰符信息;换句话说,它表示 public、final、abstract、或是否为 interface 。

  • this_class,super_class:类对应的 this 和 super 在常量池(constant_pool)中的索引。

  • interfaces_count,interfaces[]:类实现接口的数量在常量池(constant_pool)中的索引,以及每个接口的索引。

  • fields_count,fields[]:类中字段的数量以及字段的信息。字段的信息包括字段名、类型信息、修饰符、以及常量池的索引值。

  • methods_count,methods[]:类中方法的数量以及类方法的信息。方法信息包括方法名、参数的类型及数量、返回类型、修饰符、常量池的索引值、方法执行的代码和异常信息。

  • attributes_count,attributes[]:attribute_info 的结构包括多个不同的 attributes 。供 field_info、method_info 和 attribute_info 使用。

javap 反编译程序可以将类文件格式反编译为程序员可读的形式。使用 “javap -verbose” 分析 UserService.class 类,得到以下内容。

 1 Compiled from "UserService.java"
2
3 public class com.nhn.service.UserService extends java.lang.Object
4 SourceFile: "UserService.java"
5 minor version: 0
6 major version: 50
7 Constant pool:const #1 = class #2; // com/nhn/service/UserService
8 const #2 = Asciz com/nhn/service/UserService;
9 const #3 = class #4; // java/lang/Object
10 const #4 = Asciz java/lang/Object;
11 const #5 = Asciz admin;
12 const #6 = Asciz Lcom/nhn/user/UserAdmin;;// … omitted - constant pool continued …
13
14 {
15 // … omitted - method information …
16
17 public void add(java.lang.String);
18 Code:
19 Stack=2, Locals=2, Args_size=2
20 0: aload_0
21 1: getfield #15; //Field admin:Lcom/nhn/user/UserAdmin;
22 4: aload_1
23 5: invokevirtual #23; //Method com/nhn/user/UserAdmin.addUser:(Ljava/lang/String;)Lcom/nhn/user/User;
24 8: pop
25 9: return LineNumberTable:
26 line 14: 0
27 line 15: 9 LocalVariableTable:
28 Start Length Slot Name Signature
29 0 10 0 this Lcom/nhn/service/UserService;
30 0 10 1 userName Ljava/lang/String; // … Omitted - Other method information …
31 }

由于内容太长,这里只提取了部分信息。完整的内容提供了各种信息,包括常量池和每个方法体的内容。

方法 65535 字节的大小限制与 method_info struct 的内容相关。结构 method_info struct 里有代码(Code),行号表(LineNumberTable)和本地变量表(LocalVariableTable)属性,如上所示。所有代码内包括如行号表(LineNumberTable),本地变量表(LocalVariableTable)和异常表(exception_table)的属性值都是 2 字节的。因此,方法的大小不能超过行号表(LineNumberTable),本地变量表(LocalVariableTable)和异常表(exception_table)的长度,即 65535 字节。

许多人抱怨过这个方法大小的限制,JVM 规范上解释说 “可能以后会扩展” 。然而,到目前为止还没有明显的行动。考虑到 JVM 规范的特点通常在方法区加载类文件的内容相同,既要向后保持兼容又要能扩展方法的大小是十分困难的

“如果是因为 Java 编译器的错误导致类文件不正确会怎么样?或者,如果因为网络传输或文件拷贝过程出现错误,类文件会被破坏?”

为了应对这种情形,Java 类装载器(class loader)检查过程是非常严格的。JVM 规范明确规定了这个过程。

注意

我们如何验证 JVM 成功执行了类文件的验证过程?如何验证来自不同 JVM 提供商的各种各样的 JVM 是否满足 JVM 规范的要求?为了验证,Oracle 提供了一个测试工具,TCK(Technology Compatibility Kit)。TCK 通过执行上万个测试来验证 JVM 规范是否能得到满足,包括很多不正确的类文件。如果能通过 TCK 的测试,那一个 JVM 才能称为 JVM 。

和 TCK 类似,JCP(Java Community Process; http://jcp.org)会提议新的 Java 技术文档以及 Java 语言规范。对于 JCP 来说,一个文档规范,参考实现,JSR(Java Specification Request)TCK 必须要完成以满足 JSR 。想要使用以 JSR 形式提议的新 Java 技术的用户需要遵守 RI 提供方的许可,或者直接实现它并用 TCK 对实现进行测试。

参考

参考来源:

JVM Specification SE 7 - Run-Time Data Areas

2011.01 Java Bytecode Fundamentals

2012.02 Understanding JVM Internals

2013.04 JVM Run-Time Data Areas

Chapter 5 of Inside the Java Virtual Machine

2012.10 Understanding JVM Internals, from Basic Structure to Java SE 7 Features

2016.05 深入理解java虚拟机

结束

JVM 内部原理(三)— 基本概念之类文件格式的更多相关文章

  1. JVM 内部原理(四)— 基本概念之 JVM 结构

    JVM 内部原理(四)- 基本概念之 JVM 结构 介绍 版本:Java SE 7 每位使用 Java 的程序员都知道 Java 字节码在 Java 运行时(JRE - Java Runtime En ...

  2. JVM 内部原理(五)— 基本概念之 Java 虚拟机官方规范文档,第 7 版

    JVM 内部原理(五)- 基本概念之 Java 虚拟机官方规范文档,第 7 版 介绍 版本:Java SE 7 每位使用 Java 的程序员都知道 Java 字节码在 Java 运行时(JRE - J ...

  3. JVM 内部原理(二)— 基本概念之字节码

    JVM 内部原理(二)- 基本概念之字节码 介绍 版本:Java SE 7 每位使用 Java 的程序员都知道 Java 字节码在 Java 运行时(JRE - Java Runtime Enviro ...

  4. JVM 内部原理系列

    JVM 内部原理(一)— 概述 JVM 内部原理(二)— 基本概念之字节码 JVM 内部原理(三)— 基本概念之类文件格式 JVM 内部原理(四)— 基本概念之 JVM 结构 JVM 内部原理(五)— ...

  5. JVM 内部原理(六)— Java 字节码基础之一

    JVM 内部原理(六)- Java 字节码基础之一 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...

  6. JVM内部原理

    这篇文章详细描述了Java虚拟机的内在结构.下面这张图来自<The Java Virtual Machine Specification Java SE 7 Edition>,它展示了一个 ...

  7. JVM 内部原理(七)— Java 字节码基础之二

    JVM 内部原理(七)- Java 字节码基础之二 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...

  8. JVM 内部原理(一)— 概述

    JVM 内部原理(一)- 概述 介绍 版本:Java SE 7 图中显示组件将会从两个方面分别解释.第一部分涵盖线程独有的组件,第二部分涵盖独立于线程的组件(即线程共享组件). 目录 线程独享(Thr ...

  9. JVM 内部原理

    1.JVM的组成: JVM 由类加载器子系统.运行时数据区.执行引擎以及本地方法接口组成. 2.JVM的运行原理: JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器.它是一种基 ...

随机推荐

  1. 51Nod.1237.最大公约数之和 V3(莫比乌斯反演 杜教筛 欧拉函数)

    题目链接 \(Description\) \(n\leq 10^{10}\),求 \[\sum_{i=1}^n\sum_{j=1}^ngcd(i,j)\ mod\ (1e9+7)\] \(Soluti ...

  2. struts2标签在jsp页面中构建map集合,循环显示

    <s:radio name="gender" list="{'男', '女'}"></s:radio> <s:select nam ...

  3. pycharm引入numpy报错:ImportError: Importing the multiarray numpy extension module failed. Most likely you are trying to import a failed build of numpy.

    软件为pycharm,安装了anaconda. 我一开始的报错为,PyCharm中无法调用numpy,报错ModuleNotFoundError: No module named 'numpy',这个 ...

  4. JavaScript:谈谈let和const

    最近接触到ES6的一些相关新特性,想借let和const两个命令谈谈JavaScript在变量方面的改进. 由于let和const有很多相似之处,我们就先说一说let吧. 1. let添加了块级作用域 ...

  5. 解析3D标签云,其实很简单

    声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢! 最近开始用canvas搞3D了,搞得也是简单的东西,就是球体转圈.做出来后,突然想起以前看过的3D标签云,在以前觉得真心狂拽酷炫叼啊,当时 ...

  6. 命令行方式删除文件 && 文件夹

    del File: 删除文件 rmdir Folder : 删除文件夹 [貌似只能删除空目录] rm -rf Folder: 删除非空文件夹 [用windows自带的cmd提示我“rm”不是内部命令 ...

  7. C# Invoke方法

    留下备用,具体如下: Invoke()方法是U3D的一种委托机制: 1.它可以在脚本的生命周期(Start.Update.OnGUI.FixedUpdate.LateUpdate)中调用. 2.Inv ...

  8. Codeforces Round #109 (Div. 1) 题解 【ABC】

    A - Hometask 题意:给你一个字符串,然后再给你k个禁止挨在一起的字符串,问你最少删除多少个字符串,使得不会有禁忌的字符串对挨在一起.题目保证每个字符最多出现在一个禁忌中. 题解:由于每个字 ...

  9. [前端] 记录工作中遇到的各种问题(Bug,总结,记录)

    最近一年,在开发实践过程中遇到了不少问题,大多都能得到解决 部分知其原理,部分只能做到解决问题,而半年前遇到的问题,或多或少都忘得差不多了 是该记录一下一些问题,防止再遇到就得再查资料了 1. 浏览器 ...

  10. App架构师实践指南三之基础组件

    App架构师实践指南三之基础组件 1.基础组件库随着时间的增长,代码量的逐渐积累,新旧项目之间有太多可以服用的代码.下面是整理的公共代码库. 2.关于加密密钥的保护以及网络传输安全是移动应用安全最关键 ...