深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器

这篇具有很好参考价值的文章主要介绍了深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

😉😉 学习交流群:

✅✅1:这是孙哥suns给大家的福利!

✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料

🥭🥭3:QQ群:583783824   📚📚  工作微信:BigTreeJava 拉你进微信群,免费领取!

🍎🍎4:本文章内容出自上述:Spring应用课程!💞💞

💞💞5:以上内容,进群免费领取呦~ 💞💞💞💞

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

文章目录

一:方法的调用

1:概述

2:静态链接

3:动态链接

二:方法的绑定

1:绑定概念

2:早期绑定

3:晚期绑定

三:晚期绑定示例

1:编写代码

2:jclasslib查看内容

四:早期绑定示例 

1:编写代码

2:jclasslib查看内容

五:总结说明


一:方法的调用

        我们每天都在写方法的调用,但是我们能搞明白其中的原理和JVM当中的操作步骤么?这就是本文的意义。

1:概述

        官方说法:

        在JVM中,将符号引用转换为调用方法的直接引用这个操作是跟JVM当中方法的绑定机制息息相关的。

        说人话:

        上边这段话是什么意思?我这里给大家解释一下,我们javap整理完毕字节码文件之后,我们会可以在任意一个方法中查看code下的字节码指令,很多字节码指令的后边都会跟#数字这么一个概念,这个就是符号引用,这个引用指向常量池。

        所谓将符号引用转换为方法的直接引用,就是将这个字节码指令后边的符号引用,转变为真实的方法。

        下列中的#3就是符号引用。

  public void methodB();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String methodB().....
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: aload_0
         9: invokevirtual #7                  // Method methodA:()V
        12: aload_0
        13: dup
        14: getfield      #2                  // Field num:I
        17: iconst_1
        18: iadd
        19: putfield      #2                  // Field num:I
        22: return

        从上述找一个例子的话,就是将偏移地址为9的字节码指令后边的#7这个符号引用用真实的方法字面量代替

2:静态链接

        官方说法:

        当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。

        说人话:

        静态链接:这种方式在编译阶段就已经把符号引用直接转换为了直接引用。

3:动态链接

        官方说法:

        如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。

        说人话:

        动态链接:这种方式在运行阶段才能把符号引用直接转换为直接引用。

二:方法的绑定

1:绑定概念

        绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。这个不论是编译器确定还是运行期确定都只会发生一次,不会修改。

        对应的方法的绑定机制为:早期绑定 (Early Bindng)和晚期绑定(Late Binding)。

2:早期绑定

        官方说法:

        早期绑定就是指被调用的目标方法如果在编译期可知,且运行期保持不变时即可将这个方法与所属的类型进行绑定,这样一来,由于明确了被调用的目标方法究竟是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用。

        说人话:

        早期绑定是和我们的静态绑定相对应的。

3:晚期绑定

        官方说法:

        如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际的类型绑定相关的方法,这种绑定方式也就被称之为晚期绑定

        说人话:

        晚期绑定是和我们的动态绑定相对应的。

三:晚期绑定示例

1:编写代码

class Animal {
    public void eat(){
        System.out.println("动物进食");
    }
}

interface Huntable{
    void hunt();
}

class Dog extends Animal implements Huntable{
    @Override
    public void eat(){
        System.out.println("狗吃骨头");
    }

    @Override
    public void hunt() {
        System.out.println("捕食耗子,多管闲事");
    }
}

class Cat extends Animal implements Huntable{
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }

    @Override
    public void hunt() {
        System.out.println("捕食耗子,天经地义");
    }
}

public class AnimalTest{
    public void showAnimal(Animal animal){
        animal.eat();//晚期绑定
    }

    public void showHunt(Huntable h){
        h.hunt();//晚期绑定
    }

}

2:jclasslib查看内容

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

四:早期绑定示例 

1:编写代码

class Animal {
    public void eat(){
        System.out.println("动物进食");
    }
}

interface Huntable{
    void hunt();
}

class Dog extends Animal implements Huntable{
    @Override
    public void eat(){
        super.eat();//早期绑定
        System.out.println("狗吃骨头");
    }

    @Override
    public void hunt() {
        System.out.println("捕食耗子,多管闲事");
    }
}

class Cat extends Animal implements Huntable{
    public Cat(){
        super();//早期绑定
    }
    public Cat(String name){
        this();//早期绑定
    }
    
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }

    @Override
    public void hunt() {
        System.out.println("捕食耗子,天经地义");
    }
}

public class AnimalTest{
    public void showAnimal(Animal animal){
        animal.eat();//晚期绑定
    }

    public void showHunt(Huntable h){
        h.hunt();//晚期绑定
    }

}

2:jclasslib查看内容

        光标放到cat这个类上查看他的jclasslib

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言         invokeSpecial是早期绑定字节码指令,invokevirtual是晚期绑定的字节码指令。

五:总结说明

        随着高级语言的横空出世,类似于Java一样的基于面向对象的编程语言如今越来越多,尽管这类编程语言在语法风格上存在一定的差别,但是它们彼此之间始终保持着一个共性,那就是都支持封装、继承和多态等面向对象特性

        既然这一类的编程语言具备多态特性,那么自然也就具备早期绑定和晚期绑定两种绑定方式。

        Java中任何一个普通的方法其实都具备虚函数的特征,也就是运行期才能确定下来,它们相当于c++语言中的虚函数 (c++中则需要使用关键字virtual来显式定义)。

        如果在Java程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字final来标记这个方法。也就是一个方法不想被晚期绑定,直接把他给final修饰即可。

 

文章目录

一:程序计数器 

1:概念

2:官方说法

3:图解

4:特点

二:PC寄存器使用举例

1:定义一个简单的类

2: 重新编译这个类

3: 进入class文件目录

4:进行反编译

5:重点关注区域 

三:PC寄存器两个面试问题

1:问题1

2:问题2 

3:cpu时间片

4:并行、串行、并发


一:程序计数器 

1:概念

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

        JVM中的程序计数寄存器 (Program Counter Register)中(程序计数寄存器), Register 的命名源于CPU的寄存器,寄存器存储指令相关的现场信息, CPU只有把数据装载到寄存器才能够运行

        这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子)并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。 是一个软件层面的概念

        钩子怎么理解呢?烤鸭的时候一个钩子钩一只鸭子。在我们的程序中一行一行代码也是由PC寄存器这个钩子钩着,需要的时候提溜出来直接执行就好啦。

2:官方说法

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

3:图解

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

        PC寄存器是每一个新城有一份。 在方法入栈之后,对应的是一个又一个的栈帧入栈。栈帧分为局部变量表、操作数栈、动态链接、方法返回值(一个栈帧对应一个方法)。栈帧中每一个指令呢,都会有一个行号的标识,PC寄存器存储了下一条指令的地址。执行引擎根据PC寄存器中的指令获取下一条指令,执行完毕之后再去PC寄存器中去取指令执行。

4:特点

        它是一块很小的内存空间储区域。小到几乎可以忽略不计(因为只存储下一条指令的地址)。也是运行速度最快的存储区域。

        在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的。生命周期与线程的生命周期保持一致(线程私有的都会和线程声明周期一致)

        任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法(上图中的红框区域)。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址。或者,如果是在执行native方法,则是未指定值 (undefined)

        怎样理解栈的这个先进后出,后进先出呢?想想手枪中的子弹就明白了!

        它是程序控制流的指示器,分支、循环.跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

        字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

        它是唯一一个在Java 虚拟机规范中没有规定任何outotMemoryError情况的区域。

二:PC寄存器使用举例

1:定义一个简单的类

public class Dashu {
    public static void main(String[] args) throws IOException {
        int i = 10;
        int j = 20;
        int k = i +j;
    }
}

2: 重新编译这个类

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

3: 进入class文件目录

2023/10/24  22:45    <DIR>          .
2023/10/09  11:20    <DIR>          ..
2023/10/09  11:24           143,226 aaa.txt
2023/10/24  22:45               474 Dashu.class
               2 个文件        143,700 字节
               2 个目录 116,607,049,728 可用字节

4:进行反编译

D:\code\study\hadoop\shit\target\classes>javap -verbose Dashu.class
Classfile /D:/code/study/hadoop/shit/target/classes/Dashu.class
  Last modified 2023-10-24; size 474 bytes
  MD5 checksum 87503ee08b4b8f0b59290d1832fe49c7
  Compiled from "Dashu.java"
public class Dashu
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#23         // java/lang/Object."<init>":()V
   #2 = Class              #24            // Dashu
   #3 = Class              #25            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               LDashu;
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               args
  #14 = Utf8               [Ljava/lang/String;
  #15 = Utf8               i
  #16 = Utf8               I
  #17 = Utf8               j
  #18 = Utf8               k
  #19 = Utf8               Exceptions
  #20 = Class              #26            // java/io/IOException
  #21 = Utf8               SourceFile
  #22 = Utf8               Dashu.java
  #23 = NameAndType        #4:#5          // "<init>":()V
  #24 = Utf8               Dashu
  #25 = Utf8               java/lang/Object
  #26 = Utf8               java/io/IOException
{
  public Dashu();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LDashu;

  public static void main(java.lang.String[]) throws java.io.IOException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10
         2: istore_1
         3: bipush        20
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 9: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
            3       8     1     i   I
            6       5     2     j   I
           10       1     3     k   I
    Exceptions:
      throws java.io.IOException
}
SourceFile: "Dashu.java"

D:\code\study\hadoop\shit\target\classes>

5:重点关注区域 

public static void main(java.lang.String[]) throws java.io.IOException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10 //取出来一个数10
         2: istore_1         //保存到索引为1的位置
         3: bipush        20 //取出来一个数20
         5: istore_2         //保存到索引为2的位置
         6: iload_1          //从索引1处取值
         7: iload_2          //从索引2处取值
         8: iadd             //进行相加的操作
         9: istore_3         //存到索引为3的位置
        10: return           //表示main方法结束
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 9: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
            3       8     1     i   I
            6       5     2     j   I
           10       1     3     k   I
    Exceptions:
      throws java.io.IOException
}

        上边我们注释的地方有一行一行的数字,这个数字就是指令地址或者叫偏移地址。 偏移地址或者指令地址就是PC寄存器中存储的结构。

        指令地址的右侧就是执行引擎需要执行的具体的指令。具体的指令我们后边都会分析到。

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

        PC寄存器当中存储的就是这些数字。执行引擎会去我们指令地址对应的指令位置取出来具体的指令内容(去我们的局部变量表中取指令)。然后去操作咱们的栈结构中的局部变量表、操作数栈实现数据的存取,然后将我们的字节码指令翻译成为机器指令,就可以让我们的CPU帮我们做运算了。

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

三:PC寄存器两个面试问题

1:问题1

问题1:使用PC寄存器存储字节码指令地址有什么用呢?

问题2:为什么使用PC寄存器记录当前线程的执行地址呢?

        这两个本质上是一个问题:

        因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。

        JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令

深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器,# JVM专栏,1024程序员节,java,java-ee,jvm,开发语言

2:问题2 

问题2:PC寄存器为什么会被设定为线程私有? 

        我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

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

        这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。

3:cpu时间片

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

        在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。

        但在微观上:由于只有一个 CPU,一次只能处理程序要求的一部分如何处理公平,一种方法就是引入时间片,每个程序轮流执行。

4:并行、串行、并发

        并行就是多核CPU,每一个CPU都在执行具体的线程,这个叫做并行。

        串行就是单核情况下,线程排好队,一次只有一个线程再跑

        并发就是一个核CPU,多个线程来回切换抢占CPU资源。文章来源地址https://www.toymoban.com/news/detail-720584.html

到了这里,关于深入理解JVM虚拟机第十三篇:详解JVM中的程序计数器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 二十三种设计模式第十三篇--享元模式

    享元模式,主要就是一种池化方案,主要用于创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于 结构型模式 ,它提供了减少对象数量从而改善应用所需的对象结构的方式。 享元模式,尝试重用现有的同类对象,如果未找到相同匹配的对象,那么就去创

    2024年02月12日
    浏览(34)
  • 【MySQL数据库 | 第十三篇】多表查询

    多表查询是指在一个SQL语句中使用多个表进行数据查询和操作。多表查询可以对数据表之间的关系进行查询,例如可以通过连接多个表来获取更完整的数据信息。关于单表查询我们也介绍过,已经整理成文章发布:【MySQL数据库 | 第九篇】DQL操作_我是一盘牛肉的博客-CSDN博客

    2024年02月08日
    浏览(41)
  • 深入理解Java虚拟机jvm-对象的内存布局

    在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例 数据(Instance Data)和对齐填充(Padding)。 HotSpot虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈 希码(HashCode)、GC分代年龄、锁状态标志、

    2024年02月09日
    浏览(43)
  • [AIGC] 深入理解 Java 虚拟机(JVM)的垃圾回收

    一、是什么 Java 虚拟机(JVM)的垃圾回收(Garbage Collection)是一种自动内存管理机制,用于释放不再使用的对象所占用的内存空间。垃圾回收的目标是回收那些不再被程序引用的对象,以避免内存泄漏和内存溢出等问题。 二、为什么需要垃圾回收 在 Java 程序中,对象的创建

    2024年02月21日
    浏览(40)
  • [AIGC] 利用 chatgpt 深入理解 Java 虚拟机(JVM)

    Java 虚拟机(JVM)是 Java 编程语言的核心运行环境,它负责解释和执行 Java 字节码。它是 Java 程序能够跨平台运行的关键,因为不同的操作系统和硬件平台都有自己的指令集和体系结构,而 JVM 则提供了一个统一的运行环境,使得 Java 程序可以在不同的平台上无需修改就能运行

    2024年02月22日
    浏览(38)
  • “深入理解JVM:Java虚拟机的工作原理揭秘“

    标题:深入理解JVM:Java虚拟机的工作原理揭秘 摘要:本文将深入解析Java虚拟机(JVM)的工作原理,包括JVM的组成部分、类加载过程、运行时数据区域、垃圾回收机制等。通过详细的代码示例,帮助读者更好地理解JVM的内部机制。 正文: 一、JVM的组成部分 Java虚拟机是Java语

    2024年02月13日
    浏览(34)
  • 深入理解Java虚拟机:JVM高级特性与最佳实践

    Java虚拟机 Java虚拟机(Java Virtual Machine,JVM)是Java语言的核心,是执行Java二进制代码的虚拟计算机。 JVM本身是一个进程,负责解析Java程序并将其转换为特定平台可以执行的指令集。 通过JVM,Java程序可以实现“一次编写,到处运行”的特性,使Java具有很强的平台无关特性。

    2024年02月07日
    浏览(44)
  • “深入理解Java虚拟机(JVM):背后的工作原理解析“

    标题:深入理解Java虚拟机(JVM):背后的工作原理解析 摘要:本文将深入探讨Java虚拟机(JVM)的工作原理,包括内存管理、垃圾回收、即时编译器等关键概念,以及如何优化代码以提高性能。通过示例代码和详细解释,读者将对JVM的底层原理有更深入的理解。 正文: 一、

    2024年02月12日
    浏览(38)
  • PerfView专题 (第十三篇):洞察 .NET程序 的非托管句柄泄露

    前几天写了一篇 如何洞察 .NET程序 非托管句柄泄露 的文章,文中使用 WinDbg 的 !htrace 命令实现了句柄泄露的洞察,在文末我也说了,WinDbg 是以侵入式的方式解决了这个问题,在生产环境中大多数情况下是不能走附加进程的模式,所以这也是它最大的局限性。 那如何以 非侵入

    2024年02月16日
    浏览(49)
  • jvm复习,深入理解java虚拟机一:运行时数据区域

            程序计数器 (Program Counter Register) 它是程序控制流的指示器,简单来说,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器          Java虚拟机栈 (Java Virtual Machine Stack)也是线程私有的,它的生命周期 与线程相同。虚拟机栈描述

    2024年01月22日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包