天天向上教育网

除了 native 方法,几乎所有的 Ja 方法都是通虚拟机栈来实

简介: 除了 native 方法,几乎所有的 Ja 方法都是通虚拟机栈来实现方法的调用和执行(需要程序计数器、堆、方法区的配合)。

JVM运行过程众所周知, Ja 源文件通过编译器编译后,能产生相应的 .Class 文件,也就是字节码文件。

而字节码文件通过 Ja 虚拟机中的解释器,编译成特定机器上的机器码。

不同的平台都有JVM的版本,一个 Ja 源文件被编译成字节码文件,被不同平台的 JVM 翻译成特定平台下的机器码从而运行。

见下图:Ja 虚拟机组成Ja 虚拟机由三个子系统构成,分别是类加载子系统、JVM 运行时数据区和执行引擎,本文的重点是在 JVM 运行时数据区。

类加载子系统将硬盘上的字节码文件加载进内存,JVM 运行内存有一套自己的结构划分如图所示,最终程序要运行,需要操作系统分配相应的时间调度,由执行引擎去执行,才能得到最终结果。

线程私有数据:本线程私有的一块内存区域虚拟机栈Ja 虚拟机栈是线程私有的,它的生命周期与线程相同,线程启动而产生,线程结束而消亡。

Ja 虚拟机栈是描述 Ja 方法执行的内存模型,用于存储栈帧。

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常。

虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常。

除了 native 方法,几乎所有的 Ja 方法都是通虚拟机栈来实现方法的调用和执行(需要程序计数器、堆、方法区的配合)。

栈帧(Stack Frame)每个方法执行的同时会创建一个栈帧,它是虚拟机栈的基本元素。

一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。

局部变量表(Local Variable Table)一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。

该方法所需要分配的局部变量表的最大容量在将 Ja 编译为 Class 文件时已经确定。

一个局部变量表保存的是编译器可知的各种基本数据类型、对象引用和 returnAddress 类型(它指向了一条字节码指令的地址)。

虚拟机通过索引定位的方法查找相应的局部变量操作数栈(Operand Stack)虚拟机栈中的一个用于计算的临时数据存储区。

随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。

动态链接(Dynamic Linking)在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于运行时常量池。

方法返回一个方法开始执行后,只有两种退出方式:正常完成出口和异常完成出口正常完成出口指方法正常完成并退出,根据当前方法返回的字节码指令,这时有可能会有返回值传递给方法调用者(调用它的方法),或者无返回值。

无论采用何种退出方式,在方法退出后,都需要返回到方法被调用的位置,方法返回时可能需要在栈帧中保存一些信息。

程序计数器是线程私有的JVM 的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,在同一时刻一个处理器内核只会执行一条线程,处理器切换线程时并不会记录上一个线程执行到哪个位置,所以为了线程切换后依然能恢复到原位,每条线程都需要有各自独立的程序计数器。

JVM 规范中唯一没有规定 OutOfMemoryError 情况的区域程序计数器存储的是字节码文件的行号,而这个范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存。

执行 Native 方法时计数器值为空当执行 Ja 方法时,程序计数器存放 Ja 字节码的地址。

实现上可能有两种形式,一种是相对该方法字节码开始处的偏移量,叫做 bytecode index(简称 bci)。

另一种是该 Ja 字节码指令在内存的地址,叫做 bytecode pointer(简称 bcp)。

Native 方法大多通过 C 实现,它的方法体不是由 Ja 字节码构成,无法应用上述 Ja 字节码地址的概念,也就不需要存储字节码文件的行号。

Native 方法的实际执行Ja 线程总是需要以某种形式映射到 OS 线程上,HotSpot VM 目前在大多数平台上都使用 1:1 模型(原生线程模型),也就是每个 Ja 线程直接映射到一个 OS 线程上执行。

本地方法栈本地方法栈为虚拟机使用到的 Native 方法服务。

Native 方法是 Ja 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Ja 的一个接口,Ja 通过调用这个接口从而调用 C/C++ 方法。

与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

Ja 程序调用本地方法不同于虚拟机栈的入/出栈,当线程调用 native 方法时,虚拟机只是简单地动态连接并直接调用指定的 native 方法。

本地方法接口回调 JVM 中的 Ja 方法如果某个虚拟机实现的本地方法接口是使用 C 连接模型的话,那么他的本地方法栈就是 C 栈,当一个 C 函数调用另一个 C 函数时,它的栈操作是确定的。

如果本地方法接口需要回调JVM 中的 Ja 方法,该线程会保存本地方法栈的状态并进入到另一个Ja栈。

常用的 HotSpot 虚拟机选择合并了虚拟机栈和本地方法栈。

堆堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。

常用参数配置堆 GC在堆中分配的内存,由 JVM 自动垃圾回收器来管理。

从 JDK 1.8 开始,元空间(Metaspace)取代了永久代(PermGen)成为 HotSpot VM 对方法区的实现。

方法区存储加载进来的每一个类的结构信息,可以看做是将类(Class)的模板信息,保存在方法区里元空间属于本地内存JDK8 以前,永久代是堆的一部分,和新生代、老年代的地址是连续的。

JDK8 以后,元空间属于本地内存,不再属于堆的一部分,它还有一个别名叫非堆(Non-Heap),所以元空间不存在 OOM 内存溢出的情况。


以上是文章"

除了 native 方法,几乎所有的 Ja 方法都是通虚拟机栈来实

"的内容,欢迎阅读天天向上教育网的其它文章