Java 虚拟机

* 本文根据 Bill Venners 的著作 Inside Java Virtual Machine 第五章 The Java Virtual Machine 编写。

JVM(Java Virtual Machine)可以涉及到以下三个范畴:

  • 关于 Java 字节码运行环境的抽象定义。它是一种概念和规则,指导并且限制具体实现。
  • 一个 Java 字节码运行环境的具体实现。实现者根据前一条抽象定义,具体实现的 JVM。不同的实现者会有不同的实现方式,但总是要满足抽象定义的需求和限制。Sun 公司最早实现了第一个 JVM 版本。时至今日, Sun 公司被 Oracle 收购,其 JVM 也由 Oracle 维护,是目前最流行的 JVM 版本。Java 的跨平台特性也是由 JVM 作为中间件实现的,例如 Sun 公司就分别提供了  Linux 操作系统和 Windows 操作系统的 JVM。需要注意的是,JVM 并非专门运行由 Java 语言编制的程序,只要编译后的文件服务 JVM 的要求,这些编译后的字节码就能在 JVM 上运行。除了 Java 外,当前比较流行的可以无缝在 JVM 上运行的编程语言有 Kotlin、Scala 和 Groovy 等。
  •  一个具体的 Java 字节码运行环境实例。Java 程序需要运行在 JVM 中,每个 Java 程序运行时都要为其创建一个 JVM 运行环境,亦即将前一条中 JVM 的具体实现交由 CPU 来具体执行。JVM 实例以某一个 Java 类的 main 函数作为入口函数,启动一个非守护线程,然后依次执行程序代码,中间可能会启动新的线程,当所有的非守护线程都执行完毕并退出后,程序执行完毕,JVM 实例也会退出。

下图是 JVM 规范涵盖的主要元素的内部结构。


JVM 执行一个程序时,程序相关类文件字节码被加载到 method area,程序运行过程中生成的对象实例被保存在 heap,Java 方法参数、返回值、方法局部变量、计算的中间值被保存在 Java stacks,CPU 待执行的下一条指令的指针被放置在 PC registers,计算机原生方法相关的变量被保存在 native method stacks。

Method area 和 heap 被当前 JVM 实例中所运行程序的所有线程共享。每一个新的线程都会获得其独享的 PC register 和 Java Stack,当该线程执行 Java 方法时,PC register 的值指向下一条待执行的指令,其 Java stack 中保存该方法的局部变量、参数值、方法返回值(如果有)和计算中间值。线程调用了原生方法时,相关变量会被保存在 native method stacks,而原生方法的执行过程与 JVM 无关,因此不需要 JVM 维护其 PC register。方法的 Java stack 由许多 stack frame 构成,线程每次调用一个 Java 方法,JVM 都会 push 一个新的 stack frame 到该线程的 Java stack 供当前方法使用,方法执行完毕后,JVM 又会 pop 此方法的 stack frame。因此线程 Java stack 栈顶永远是正在被执行的 Java 方法的 stack frame。

Class Loader subsystem 负责将查找以及将 class 文件加载到 method area。JVM 有两种 class loader:primordial class loader(初始类加载器) 和 class loader objects。初始类加载器是 JVM 实现的一部分,通常由其它语言(例如 Sun 公司的 JVM 使用 C++)写成;class loader objects 则是 Java 程序的一部分,通常由 Java 语言编写并能够编译成符合要求的 class 文件。JVM 的实现者在实现 JVM 的同时,也会提供一些必须的或者常用的类库来帮助开发者,这些类库中就包含了一些 class loader objects。例如 Sun 公司的 JVM,我们可以看到可执行文件 java (启动 jvm ),以及一个 lib 文件夹。lib 文件夹中 rt.jar 就是原生的类库,又称为 Java API。JVM 要求的初始类加载器伴随着可执行文件 java 一起实现,rt.jar 中有 ExtClassLoader 和 AppClassLoader。

初始类加载器用来加载 Java 的核心类库如 rt.jar, resources.jar, charsets.jar 等,其中有 java.lang.*,java.util.* 等类,核心类库默认位于 $JAVA_HOME/lib 下,启动时也可以使用 -Xbootclasspath 参数指定核心类库所在路径。但是初始类加载器只会识别加载特定的 jar 包和类,例如 rt.jar,以 java, javax, sun 等开头的包等。

扩展类加载器(extensions class loader)ExtClassLoader 用来加载 Java 的扩展库,默认路径是 $JAVA_HOME/lib/ext,也可以在启动时由参数 -Djava.ext.dir 指定。开发人员也可以直接使用 ExtClassLoader 来加载类。

系统类加载器(system class loader)AppClassLoader 用来加载当前 Java 程序 classpath 中的 Java 类,亦即开发人员自己编写的类。

不同类加载器加载的类位于不同的命名空间下,即使是同一个类,如果该类被不同的类加载器同时加载,JVM 也会视其为不同的类,如果显式转换由不同类加载器加载的同一个类,将会出现 ClassCastException 异常。例如,允许 Spring Boot 自动重启时,Spring Boot 使用 RestartClassLoader 来加载应用 classpath 中的类,而在某些应用了反射机制加载类的地方,则会使用 AppClassLoader 用来加载当前 Java 程序 classpath 中的 Java 类,这样就会导致类转换的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值