JVM_02 运行时数据区 1-[程序计数器 + 虚拟机栈 + 本地方法栈]

内存与线程

1、内存

内存是非常重要的系统资源,是硬盘和 CPU 的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略,保证了 JVM 的高效稳定运行。不同的 jvm 对于内存的划分方式和管理机制存在着部分差异(对于 Hotspot 主要指方法区)

170ecae266df65ba.png

(图源阿里)JDK8 的元数据区 +JIT 编译产物 就是 JDK8 以前的方法区

2、分区介绍

Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。

如图,灰色的区域为单独线程私有的,红色的为多个线程共享的,即

170ecae9790e6eac.png

一般来说,jvm 优化 95% 是优化堆区,5% 优化的是方法区

3、线程

4、JVM 系统线程

程序计数器(PC 寄存器)

JVM 中的程序计数寄存器(Program Counter Register)中,Register 的命名源于 CPU 的寄存器,寄存器存储指令相关的现场信息。CPU 只有把数据装载到寄存器才能够运行。JVM 中的 PC 寄存器是对物理 PC 寄存器的一种抽象模拟。

1、作用

PC 寄存器是用来存储指向下一条指令的地址,也即将将要执行的指令代码。由执行引擎读取下一条指令。

1.1 代码示例

利用 javap -v xxx.class 反编译字节码文件,查看指令等信息

170ecaef254ef627.png

1.2 面试常问

1.使用 PC 寄存器存储字节码指令地址有什么用呢?/ 为什么使用 PC 寄存器记录当前线程的执行地址呢?

因为 CPU 需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行
JVM 的字节码解释器就需要通过改变 PC 寄存器的值来明确下一条应该执行什么样的字节码指令

2.PC 寄存器为什么会设定为线程私有

我们都知道所谓的多线程在一个特定的时间段内指回执行其中某一个线程的方法,CPU 会不停地做任务切换,这样必然会导致经常中断或恢复,如何保证分毫无差呢?

为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个 PC 寄存器。每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。

由于 CPU 时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。

1.3 时间片

CPU 时间片即 CPU 分配各各个程序的时间,每个线程被分配一个时间段。称作它的时间片。

在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。
但在微观上:由于只有一个 CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。

并行与并发:
并行:同一时间多个线程同时执行;
并发:一个核快速切换多个线程,让它们依次执行,看起来像并行,实际上是并发。

虚拟机栈

1、概述

1.1 背景

由于跨平台性的设计,Java 的指令都是根据栈来设计的。不同平台 CPU 架构不同,所以不能设计为基于寄存器的。
优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。

1.2 内存中的堆与栈

1.3 虚拟机栈是什么

1.4 栈的特点

1.5 栈中可能出现的异常

Java 虚拟机规范允许 Java 栈的大小是动态的或者是固定不变的

1.6 设置栈的内存大小

我们可以使用参数-Xss 选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。 (IDEA 设置方法:Run-EditConfigurations-VM options 填入指定栈的大小-Xss256k)

2、栈的存储结构和运行原理

170ecafb0c5ada68.png

2.1 栈帧的内部结构

每个栈帧中存储着:

170ecafe0fab0cb2.png

3、局部变量表(Local Variables)

3.1 概述

利用 javap 命令对字节码文件进行解析查看局部变量表,如图:

170ecb01f86381ee.png

也可以在 IDEA 上安装 jclasslib byte viewcoder 插件查看字节码信息,以 main()方法为例

170ecb062339c98b.png

170ecb0825056429.png

170ecb0cf610a812.png

3.2 变量槽 slot 的理解与演示

QQ 截图 20201005203419.png

public class LocalVariablesTest {

    private int count = 1;
    //静态方法不能使用this
    public static void testStatic(){
        //编译错误,因为this变量不存在与当前方法的局部变量表中!!!
        System.out.println(this.count);
    }
}

3.3 slot 的重复利用

栈帧中的局部变量表中的槽位是可以重复利用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的

private void test2() {
        int a = 0;
        {
            int b = 0;
            b = a+1;
        }
        //变量c使用之前以及经销毁的变量b占据的slot位置
        int c = a+1;
    }

3.4 静态变量与局部变量的对比及小结

变量的分类:

补充:

4、操作数栈(Operand Stack)

栈 :可以使用数组或者链表来实现

170ecb180342dcf0.png

4.1 概述

结合下面的图来看一下一个方法(栈帧)的执行过程:

①15 入栈;② 存储 15,15 进入局部变量表;

170ecb1c4797b788.png

③ 压入 8;④ 存储 8,8 进入局部变量表;

170ecb53336d048b.png

⑤ 从局部变量表中把索引为 1 和 2 的数据取出来,放到操作数栈;⑥iadd 相加操作,8 和 15 出栈;

170ecb55fd42e99f.png

⑦iadd 操作结果 23 入栈;⑧ 将 23 存储在局部变量表索引为 3 的位置上;

170ecb5802369d83.png

4.2 栈顶缓存技术 ToS(Top-of-Stack Cashing)

5、动态链接(Dynamic Linking)

170ecb779318dd20.png

5.1 常量池的作用

为了提供一些符号和常量,便于指令的识别。

170ecb79de12c6c0.png

170ecb7b7f2457c7.png

5.2 方法的调用

在 JVM 中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关

对应的方法的绑定机制为:早期绑定(Early Binding)和晚期绑定(Late Bingding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。

随着高级语言的横空出世,类似于 Java 一样的基于面向对象的编程语言如今越来越多,尽管这类编程语言在语法风格上存在一定的差别,但是它们彼此之间始终保持着一个共性,那就是都支持封装,集成和多态等面向对象特性,既然这一类的编程语言具备多态特性,那么自然也就具备早期绑定和晚期绑定两种绑定方式。

Java 中任何一个普通的方法其实都具备虚函数的特征,它们相当于 C++ 语言中的虚函数(C++ 中则需要使用关键字 virtual 来显式定义)。如果在 Java 程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字 final 来标记这个方法。

5.3 虚方法和非虚方法

非虚方法

子类对象的多态性使用前提:①类的继承关系②方法的重写

虚拟机中提供了以下几条方法调用指令:

普通调用指令:

1.invokestatic:调用静态方法,解析阶段确定唯一方法版本;

2.invokespecial:调用方法、私有及父类方法,解析阶段确定唯一方法版本;

3.invokevirtual 调用所有虚方法;

4.invokeinterface:调用接口方法;

动态调用指令:

5.invokedynamic:动态解析出需要调用的方法,然后执行 .

前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而 invokedynamic 指令则支持由用户确定方法版本。其中 invokestatic 指令和 invokespecial 指令调用的方法称为非虚方法,其余的(final 修饰的除外)称为虚方法。

关于 invokedynamic 指令
动态类型语言和静态类型语言

5.4 方法重写的本质

IllegalAccessError 介绍
程序视图访问或修改一个属性或调用一个方法,这个属性或方法,你没有权限访问。一般的,这个会引起编译器异常。这个错误如果发生在运行时,就说明一个类发生了不兼容的改变。

5.5 虚方法表

170ecb7f8233cc27.png

6、方法返回地址(Return Address)

当一个方法开始执行后,只有两种方式可以退出这个方法:

  1. 执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者,简称正常完成出口;
  1. 在方法执行的过程中遇到了异常(Exception),并且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜素到匹配的异常处理器,就会导致方法退出,简称异常完成出口

方法执行过程中抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码。

170ecb82ce5a6a35.png

7、一些附加信息

栈帧中还允许携带与 Java 虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息。(很多资料都忽略了附加信息)

8 虚拟机栈的 5 道面试题

1.举例栈溢出的情况?(StackOverflowError)

2.调整栈的大小,就能保证不出现溢出么?

3.分配的栈内存越大越好么?

4.垃圾回收是否会涉及到虚拟机栈?

QQ 截图 20201005220910.png

5.方法中定义的局部变量是否线程安全?

本地方法栈

  • 后端
    37 引用 • 115 回帖 • 1 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    2812 引用 • 8043 回帖 • 749 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    152 引用 • 115 回帖 • 3 关注

赞助商 我要投放

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...