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

介绍

版本: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 版

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

内容

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

在 2011 年 7 月 28 日,Oracle 发布了 Java SE 7 并更新了 JVM 官方规范文档至 Java SE 7 的版本。在 1999 年发布《Java 虚拟机官方规范文档,第二版》后,Oracle 花了 12 年时间做这版更新。更新版本的内容包括这 12 年来积累的各种变更修改,规范文档的描述更为清晰。除此之外,它还反映了《Java 语言规范文档,第七版》的内容。主要的更新概括如下:

  • Java SE 5.0 引入泛型,支持方法的参数变量。
  • 字节码验证过程的技术从 Java SE 6 开始发生变化。
  • 增加 invokedynamic 指令以及相关的类文件格式支持动态类型语言。
  • 删除了对于 Java 语言本身的概念性描述,并将其归入《Java 语言规范文档》中。
  • 删除了关于 Java 线程和锁的描述,并将其写入《Java 语言规范文档》。

最大的改变要数增加 invokedynamic 指令。这也意味着 JVM 内部指令集发生了变化,也就是说 JVM 从 Java SE 7 开始支持类型非固定的动态类型语言,如脚本语言,以及动态的 Java 语言。之前没有使用的操作码(OpCode)186 被应用到新指令 invokedynamic 以及新的类文件格式中以支持动态性,

由 Java 编译器 Java SE 7 创建的类文件版本是 51.0 。Java SE 6 的版本是 50.0 。类文件格式发生了很大变化,因此 51.0 版本的类文件不能运行于 Java SE 6 的 JVM 。

尽管有如此之多的变化,Java 方法的 65535 字节长度限制并没有被移除。除非 JVM 类文件格式发生了创新式的变化,否则它也不太可能在将来移除。

Oracle Java SE 7 VM 支持 G1 ,这个新的垃圾回收机制;不过,它仅限于 Oracle JVM ,所以 JVM 本身并不受限于任何垃圾回收机制。因此,JVM 官方规范文档并没有对此进行描述。

分支语句中的字符串

Java SE 7 增加了多种语法和特性。不过,与 Java SE 7 中语言发生的许多变化相比,JVM 并没有发生很多变化。那么,Java SE 7 的新特性是如何实现的呢?我们通过反编译看看 String 在分支语句中(一个将字符串传入 switch() 语句进行比较的功能)的实现方式。

有如下代码:

// SwitchTest
public class SwitchTest {
public int doSwitch(String str) {
switch (str) {
case "abc": return 1;
case "123": return 2;
default: return 0;
}
}
}

因为它是 Java SE 7 的新功能,所以它不能使用 Java SE 6 或更低版本的编译器来编译。用 Java SE 7 的 javac 编译它。用 javap -c 显示编译结果:

C:Test>javap -c SwitchTest.classCompiled from "SwitchTest.java"
public class SwitchTest {
public SwitchTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public int doSwitch(java.lang.String);
Code:
0: aload_1
1: astore_2
2: iconst_m1
3: istore_3
4: aload_2
5: invokevirtual #2 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 2
48690: 50
96354: 36
default: 61
}
36: aload_2
37: ldc #3 // String abc
39: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 61
45: iconst_0
46: istore_3
47: goto 61
50: aload_2
51: ldc #5 // String 123
53: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 61
59: iconst_1
60: istore_3
61: iload_3
62: lookupswitch { // 2
0: 88
1: 90
default: 92
}
88: iconst_1
89: ireturn
90: iconst_2
91: ireturn
92: iconst_0
93: ireturn

字节码的内容要比 Java 源代码多很多。首先,可以看到 lookupswitch 指令已经被 switch() 语句使用到字节码中。但是,有两个 lookupswitch 指令而不是一个。反编译后可以看到 int 传入 switch() 语句,但是只有一个 lookupswitch 指令用到了。也就是说 switch() 语句被拆分成了两个语句来处理字符串。查看 #5、#39 和 #53 号字节指令看 switch() 语句是如何处理字符串的。

在 #5 和 #8 号字节中,首先,hashCode() 方法被执行,然后 switch(int) 是通过使用 hashCode() 方法的运行结果来执行的。在 lookupswitch 指令的括号中,分支根据 hashCode 的值来定位到不同地方。字符串 “abc” 的 hashCode 结果是 96354 ,被定位到 #36 号字节。字符串 “123” 的 hashCode 结果是 48690 ,被定位到 #50 号字节。

在 #36、#37、#39 和 #42 号字节中,可以发现接收的 str 变量值作为参数与 String “abc” 和 equals() 方法比较。如果结果相同,“0” 被插入到 #3 号本地变量列表的索引位置,字符串被移动到 #61 号字节。

通过这种方式,在 #50、#51、#53 和 #56 字节中,可以发现接收的 str 变量值作为参数与 String “123” 和 equals() 方法比较。如果结果相同,“1” 被插入到 #3 号本地变量列表的索引位置,字符串被移动到 #61 号字节。

在 #61 和 #62 号字节中,以 #3 号本地变量列表的索引位置的值,如:“0”、“1” 或其他值,进行 lookupswitch 和分支处理。

换句话说,在 Java 代码中,接收的 str 变量值作为 switch() 参数使用 hashCode() 方法和 equals() 方法进行比较。switch() 方法根据 int 值的结果执行。

在这个结果中,被编译的字节码与之前的 JVM 规范文档并没有任何不同。Java SE 7 的新特性,字符串分支语句,是由 Java 编译器来处理的,而不是 JVM 本身。以类似的方式,Java SE 7 的其他新特性也是通过 Java 编译器进行处理的。

总结

这里评审 Java 语言是如何设计让 Java 可以更容易的使用没有太大必要。有很多 Java 程序员并没有对 JVM 有很深入的了解,却也开发出了很多优秀的应用和库。不过,如果能够深入理解 JVM ,我们就能处理这些例子中出现的问题。

除了这里提到的内容,JVM 有很多特性和技术。JVM 规范文档为 JVM 厂商提供了灵活的空间,让他们应用各种不同的技术从而提供更好的性能。特别是垃圾回收技术,它被大多数语言使用,提供与 VM 类似的使用方式以及最新最前沿的性能优化技术。但是,这些内容在很多卓越的研究中都会被讨论到,在此不作深入解释。

参考

参考来源:

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 内部原理(五)— 基本概念之 Java 虚拟机官方规范文档,第 7 版的更多相关文章

  1. JVM 内部原理系列

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

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

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

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

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

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

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

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

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

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

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

  7. JVM内部原理

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

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

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

  9. JVM规范系列第2章:Java虚拟机结构

    本规范描述的是一种抽象化的虚拟机的行为,而不是任何一种(译者注:包括 Oracle 公司自己的 HotSpot 和 JRockit 虚拟机)被广泛使用的虚拟机实现. 记住:JVM规范是一种高度抽象行为 ...

随机推荐

  1. 你还在为无法完美卸载SQL Server 2008 R2而烦恼吗?

    你还在为无法完美卸载SQL Server 2008 R2而烦恼吗? 本文摘抄来自:http://blog.csdn.net/u013058618/article/details/50265961  小 ...

  2. BZOJ.2199.[USACO2011 Jan]奶牛议会(2-SAT)

    题目链接 建边不说了.对于议案'?'的输出用拓扑不好判断,直接对每个议案的结果DFS,看是否会出现矛盾 Tarjan也用不到 //964kb 76ms #include <cstdio> ...

  3. BZOJ.4542.[HNOI2016]大数(莫队)

    题目链接 大数除法是很麻烦的,考虑能不能将其条件化简 一段区间[l,r]|p,即num[l,r]|p,类似前缀,记后缀suf[i]表示[i,n]的这段区间代表的数字 于是有 suf[l]-suf[r+ ...

  4. Python3科学计算库概况

    Python3科学计算常见库入门 Numpy快速数据处理库 参见我的博客 http://www.cnblogs.com/brightyuxl/p/8981294.html http://www.cnb ...

  5. 初试Django的缓存系统

    初试Django的缓存系统 现在我网页的逻辑是,响应请求,查找数据库相关信息,渲染模版生成最终页面,最后返回.使用缓存后就是将这个页面保存一段时间,在有响应请求相同页面的时候,可以直接返回,不用再做那 ...

  6. tableview 选中一行后,不显示选中颜色

    tableview 选中一行后,不显示选中颜色 千万不要将tableview的allowsSelection设置成NO,那样的话可能导致tableview不能响应点击动作. 应该使用:cell.sel ...

  7. db2 表空间容量

    Db2 connect to xxx Db2 “LIST TABLESPACES SHOW DETAIL” Tablespace ID = 7 Name = TSASNAA Type = Databa ...

  8. Linux中日期的加减运算

    Linux中日期的加减运算 目录 在显示方面 在设定时间方面 时间的加减 正文 date命令本身提供了日期的加减运算. date 可以用来显示或设定系统的日期与时间. 回到顶部 在显示方面 使用者可以 ...

  9. Java基础-多线程-①线程的创建和启动

    简单阐释进程和线程 对于进程最直观的感受应该就是“windows任务管理器”中的进程管理: (计算机原理课上的记忆已经快要模糊了,简单理解一下):一个进程就是一个“执行中的程序”,是程序在计算机上的一 ...

  10. mui 总结

    出框框 js内容 mui(".mui-popover").popover('toggle');         点击“弹出框框”就会弹出这个有class="mui-pop ...