jvm系列(3)--运行时数据区Runtime

news2025/1/12 22:58:43

目录

  • 运行时数据区概述及线程
    • 前言
    • 运行时数据区结构
      • 运行时数据区与内存
      • 线程的内存空间
      • Runtime类
    • 线程
      • JVM 线程
      • JVM 系统线程
  • 程序计数器(PC寄存器)
    • PC寄存器介绍
    • PC寄存器的作用
    • 举例
    • 两个面试题
    • CPU 时间片
  • 本地方法接口
    • 本地方法
    • 举例
    • 为什么要使用 Native Method?
      • 与Java环境外交互
      • 与操作系统的交互
      • Sun’s Java
      • 本地方法的现状
  • 本地方法栈

运行时数据区概述及线程

前言

本节主要讲的是运行时数据区,也就是下图这部分,它是在类加载完成后的阶段

当我们通过前面的:类的加载 --> 验证 --> 准备 --> 解析 --> 初始化,这几个阶段完成后,就会用到执行引擎对我们的类进行使用,同时执行引擎将会使用到我们运行时数据区

类比一下也就是大厨做饭,我们把大厨后面的东西(切好的菜,刀,调料),比作是运行时数据区。而厨师可以类比于执行引擎,将通过准备的东西进行制作成精美的菜品。

运行时数据区结构

运行时数据区与内存

  1. 内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着部分差异。结合JVM虚拟机规范,来探讨一下经典的JVM内存布局。

  2. 我们通过磁盘或者网络IO得到的数据,都需要先加载到内存中,然后CPU从内存中获取数据进行读取,也就是说内存充当了CPU和磁盘之间的桥梁

下图来自阿里巴巴手册JDK8

线程的内存空间

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

  2. 灰色的为单独线程私有的,红色的为多个线程共享的。即:

    • 线程独有:独立包括程序计数器、栈、本地方法栈
    • 线程间共享:堆、堆外内存(永久代或元空间、代码缓存)

Runtime类

每个JVM只有一个Runtime实例。即为运行时环境,相当于内存结构的中间的那个框框:运行时环境。

线程

JVM 线程

  1. 线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行
  2. 在Hotspot JVM里,每个线程都与操作系统的本地线程直接映射
    • 当一个Java线程准备好执行以后,此时一个操作系统的本地线程也同时创建。Java线程执行终止后,本地线程也会回收
  3. 操作系统负责将线程安排调度到任何一个可用的CPU上。一旦本地线程初始化成功,它就会调用Java线程中的run()方法

关于线程,并发可以看笔者的Java并发系列

JVM 系统线程

  • 如果你使用jconsole或者是任何一个调试工具,都能看到在后台有许多线程在运行。这些后台线程不包括调用public static void main(String[])的main线程以及所有这个main线程自己创建的线程。

  • 这些主要的后台系统线程在Hotspot JVM里主要是以下几个:

  1. 虚拟机线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型括"stop-the-world"的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销
  2. 周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行
  3. GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持
  4. 编译线程:这种线程在运行时会将字节码编译成到本地代码
  5. 信号调度线程:这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行处理

程序计数器(PC寄存器)

PC寄存器介绍

官方文档网址:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

  1. JVM中的程序计数寄存器(Program Counter Register)中,Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。
  2. 这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟
  3. 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。
  4. 在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
  5. 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefned)。
  6. 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
  7. 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
  8. 它是唯一一个在Java虚拟机规范中没有规定任何OutofMemoryError情况的区域。

PC寄存器的作用

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

举例

public class PCRegisterTest {

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

        String s = "abc";
        System.out.println(i);
        System.out.println(k);

    }
}

查看字节码

看字节码的方法:https://blog.csdn.net/21aspnet/article/details/88351875

Classfile /F:/IDEAWorkSpaceSourceCode/JVMDemo/out/production/chapter04/com/atguigu/java/PCRegisterTest.class
  Last modified 2020-11-2; size 675 bytes
  MD5 checksum 53b3ef104479ec9e9b7ce5319e5881d3
  Compiled from "PCRegisterTest.java"
public class com.atguigu.java.PCRegisterTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#26         // java/lang/Object."<init>":()V
   #2 = String             #27            // abc
   #3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #30.#31        // java/io/PrintStream.println:(I)V
   #5 = Class              #32            // com/atguigu/java/PCRegisterTest
   #6 = Class              #33            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/atguigu/java/PCRegisterTest;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               i
  #19 = Utf8               I
  #20 = Utf8               j
  #21 = Utf8               k
  #22 = Utf8               s
  #23 = Utf8               Ljava/lang/String;
  #24 = Utf8               SourceFile
  #25 = Utf8               PCRegisterTest.java
  #26 = NameAndType        #7:#8          // "<init>":()V
  #27 = Utf8               abc
  #28 = Class              #34            // java/lang/System
  #29 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
  #30 = Class              #37            // java/io/PrintStream
  #31 = NameAndType        #38:#39        // println:(I)V
  #32 = Utf8               com/atguigu/java/PCRegisterTest
  #33 = Utf8               java/lang/Object
  #34 = Utf8               java/lang/System
  #35 = Utf8               out
  #36 = Utf8               Ljava/io/PrintStream;
  #37 = Utf8               java/io/PrintStream
  #38 = Utf8               println
  #39 = Utf8               (I)V
{
  public com.atguigu.java.PCRegisterTest();
    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 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/atguigu/java/PCRegisterTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=5, 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: ldc           #2                  // String abc
        12: astore        4
        14: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: iload_1
        18: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        21: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        24: iload_3
        25: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        28: return
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 6
        line 14: 10
        line 15: 14
        line 16: 21
        line 18: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      29     0  args   [Ljava/lang/String;
            3      26     1     i   I
            6      23     2     j   I
           10      19     3     k   I
           14      15     4     s   Ljava/lang/String;
}
SourceFile: "PCRegisterTest.java"
  • 左边的数字代表指令地址(指令偏移),即 PC 寄存器中可能存储的值,然后执行引擎读取 PC 寄存器中的值,并执行该指令

两个面试题

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

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

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

PC寄存器为什么被设定为私有的?

  1. 我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
  2. 由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
  3. 这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。

注意并行和并发的区别,笔者的并发系列有讲

CPU 时间片

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

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

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

本地方法接口

本地方法

  1. 简单地讲,一个Native Method是一个Java调用非Java代码的接囗一个Native Method是这样一个Java方法:该方法的实现由非Java语言实现,比如C。这个特征并非Java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern 告知C++编译器去调用一个C的函数。
  2. “A native method is a Java method whose implementation is provided by non-java code.”(本地方法是一个非Java的方法,它的具体实现是非Java代码的实现)
  3. 在定义一个native method时,并不提供实现体(有些像定义一个Java interface),因为其实现体是由非java语言在外面实现的。
  4. 本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序。

举例

需要注意的是:标识符native可以与其它java标识符连用,但是abstract除外

public class IHaveNatives {
    public native void Native1(int x);

    public native static long Native2();

    private native synchronized float Native3(Object o);

    native void Native4(int[] ary) throws Exception;
    
}

为什么要使用 Native Method?

Java使用起来非常方便,然而有些层次的任务用Java实现起来不容易,或者我们对程序的效率很在意时,问题就来了。

与Java环境外交互

有时Java应用需要与Java外面的硬件环境交互,这是本地方法存在的主要原因。你可以想想Java需要与一些底层系统,如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解Java应用之外的繁琐的细节。

与操作系统的交互

  1. JVM支持着Java语言本身和运行时库,它是Java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。
  2. 然而不管怎样,它毕竟不是一个完整的系统,它经常依赖于一底层系统的支持。这些底层系统常常是强大的操作系统。
  3. 通过使用本地方法,我们得以用Java实现了jre的与底层系统的交互,甚至JVM的一些部分就是用C写的
  4. 还有,如果我们要使用一些Java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。

Sun’s Java

  1. Sun的解释器是用C实现的,这使得它能像一些普通的C一样与外部交互。jre大部分是用Java实现的,它也通过一些本地方法与外界交互。
  2. 例如:类java.lang.Thread的setPriority()方法是用Java实现的,但是它实现调用的是该类里的本地方法setPriority0()。这个本地方法是用C实现的,并被植入JVM内部在Windows 95的平台上,这个本地方法最终将调用Win32 setpriority() API。这是一个本地方法的具体实现由JVM直接提供,更多的情况是本地方法由外部的动态链接库(external dynamic link library)提供,然后被JVM调用。

本地方法的现状

目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。

本地方法栈

  1. Java虚拟机栈于管理Java方法的调用,而本地方法栈用于管理本地方法的调用
  2. 本地方法栈,也是线程私有的。
  3. 允许被实现成固定或者是可动态扩展的内存大小(在内存溢出方面和虚拟机栈相同)
    • 如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个stackoverflowError 异常。
    • 如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个outofMemoryError异常。
  4. 本地方法一般是使用C语言或C++语言实现的。
  5. 它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。

注意事项

  1. 当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。
    • 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区
    • 它甚至可以直接使用本地处理器中的寄存器
    • 直接从本地内存的堆中分配任意数量的内存
  2. 并不是所有的JVM都支持本地方法。因为Java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。如果JVM产品不打算支持native方法,也可以无需实现本地方法栈。
  3. 在Hotspot JVM中,直接将本地方法栈和虚拟机栈合二为一。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/166598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tomcat打破双亲委派模型

tomcat的类加载器结构tomcat的类加载&#xff08;loadClass&#xff09;过程和原本的双亲委派模型思路差不多&#xff0c;先看有没有加载过。先在本地 Cache 查找该类是否已经加载过&#xff0c;也就是说 Tomcat 的类加载器是否已经加载过这个类。如果 Tomcat 类加载器没有加载…

【学习笔记】【Pytorch】张量(Tensor)的基础操作

【学习笔记】【Pytorch】张量&#xff08;Tensor&#xff09;的基础操作一、创建张量1.使用数据创建张量2.无需数据的创建选项3.torch.Tensor与torch.tensor的区别4.PyTorch中张量的创建方法的选择二、张量的属性1.张量的 torch.dtype2.张量的 torch.device3.张量的 torch.layo…

阿维塔冲击年10万台订单,第二款车型Q2发布

1月13日&#xff0c;阿维塔科技在重庆总部召开渠道合作伙伴大会。今年&#xff0c;阿维塔计划推出&#xff1a; •阿维塔11后驱版本 •并发布第二款产品&#xff0c;代号E12&#xff0c;定位中大型轿车。阿维塔今年计划冲击10万辆订单目标。在当前CHN平台的基础上&#xff0c;阿…

Openresty记录笔记

最近由于项目需要学习了安全代理的相关知识&#xff0c;其实刚开始的时候是非常需要一个入门的介绍&#xff0c;大概说明下这个到底是个什么东西&#xff0c;能干啥&#xff0c;简单的原理是什么&#xff0c;为此我记录下我看完用完的心得&#xff0c;记录成笔记。 一般我们代码…

Redis 持久化详解

目录一、简介二、RDB持久化2.1、SAVE2.2、BGSAVE2.3、SAVE选项2.4、RDB文件结构2.5、RDB文件载入三、AOF持久化3.1、开启AOF功能3.2、配置AOF文件的冲洗频率3.3、AOF重写3.3.1、BGREWRITEAOF命令&#xff08;手动&#xff09;3.3.2、AOF重写配置选项&#xff08;自动&#xff0…

Android | Service

Android Service Service 概念 实现程序后台运行的解决方案&#xff0c;一种可在后台执行长时间运行操作而不提供界面的应用组件。Service 的运行不依赖于任何用户界面&#xff0c;即使程序被切换到后台&#xff0c;或者用户打开了另外一个应用程序&#xff0c;Service 仍然能…

Vue3——第十五章(计算属性:computed)

一、基础示例 模板中的表达式虽然方便&#xff0c;但也只能用来做简单的操作。如果在模板中写太多逻辑&#xff0c;会让模板变得臃肿&#xff0c;难以维护。推荐使用计算属性来描述依赖响应式状态的复杂逻辑。 在这里定义了一个计算属性 publishedBooksMessage。computed() 方…

【设计模式】创建型模式·原型模式

设计模式学习之旅(五) 查看更多可关注后查看主页设计模式DayToDay专栏 一. 概述 用一个已经创建的实例作为原型&#xff0c;通过复制(克隆)该原型对象来创建一个和原型对象相同的新对象。 原型模式包含如下角色&#xff1a; 抽象原型类&#xff1a;规定了具体原型对象必须实现…

Java基础(二)

1.标识符标识符&#xff1a;由数字、字符、下划线、$组成&#xff08;不能以数字、下划线开头&#xff09;java严格区分大小写2.命名规范包名&#xff1a;多单词组成时所有字母全部小写类名、接口名&#xff1a;多单词组成时&#xff0c;所有单词首字母大写变量名、方法名&…

屏幕录制工具哪个好用?分享3款相见恨晚的软件

在我们的日常生活中&#xff0c;我们经常使用截图和手机屏幕记录功能来记录一些重要的内容。然而&#xff0c;录制的图片清晰度很低&#xff0c;或者需要不断的截图&#xff0c;这很容易出错一些重要的内容&#xff0c;这个时候就需要进行录屏了。那么电脑上的屏幕录制工具哪个…

group by详解

group by功能 在SQL中group by主要用来进行分组统计&#xff0c;分组字段放在group by的后面&#xff1b;分组结果一般需要借助聚合函数实现。 group by语法结构 1、常用语法 语法结构 SELECT column_name1,column_name2, … 聚合函数1,聚合函数2 , … FROM table_name GROUP…

电脑删除了大文件怎么恢复?看看这四种方法

电脑能够帮助我们存储大量的文件&#xff0c;比如视频、文档、音频等&#xff0c;但是随着时间的流逝&#xff0c;有些文件所存在的意义也变得毫无价值了&#xff0c;这时候很多小伙伴都会选择删除操作&#xff0c;可是由于电脑磁盘内容过多&#xff0c;容易面临重要文件被误删…

硬件仿真加速器与原型验证平台

基于软件仿真工具对于动辄几百万门的ASIC验证而言&#xff0c;几乎显得力不从心。不管是从成本还是从性能的角度来看&#xff0c;使用硬件仿真器或者基于FPGA的原型验证平台&#xff0c;几乎是验证工程师的不二法门。因为基于硬件的环境能够极大的提高验证的速度&#xff0c;增…

Promise(基础)

Promise是什么 1.promise是一门新的技术&#xff08;ES6规范&#xff09; 2.Promise是JS中一编程的解决方案&#xff08;旧的解决方案是单纯的使用回调函数&#xff09; 3.promise一个构造函数&#xff0c;promise队形用来封装一个一步操作并可以获取其成功/失败的结果值。 注…

sparksql案例实操

sparksql案例实操解决语句如下 select * from( select , rank()over(partition by area order by clickCnt desc) from(select area, product_name, count()as clickCnt from( select a.*, p.product_name, c.area, c.city_name from user_visit_action a join product_info p…

Dubbo与Spring集成

Dubbo框架常被当作第三方框架集成到应用中&#xff0c;当Spring集成Dubbo框架后&#xff0c;为什么在编写代码的时候&#xff0c;只用了DubboReference注解就可以调用提供方的服务了呢&#xff1f;这篇笔记就是分析Dubbo框架是怎么与Spring结合的。 现状integration层代码编写…

关于嵌入式学习和规划,求指点?

在知乎上收到的一个提问问题&#xff1a;各位大佬好&#xff0c;我先说说基本情况&#xff0c;28岁&#xff0c;北京&#xff0c;嵌入式软开&#xff0c;军工行业。硕士毕业一年半。工作不忙收获很少&#xff0c;造成我自己特别迷茫&#xff0c;没有了方向&#xff0c;自己学没…

【C++】Hash闭散列

目录 一、哈希的概念 1.1 哈希冲突 1.2 哈希函数 1.3 装载因子 二、闭散列 2.1 线性探测 2.2 Insert 插入 2.3 Find 查找 2.4 Erase删除 2.5 插入复杂类型 2.6 二次探测 三、源代码与测试用例 3.1 hash源代码 3.2 测试用例 一、哈希的概念 在前面学习了二叉搜索…

多巴胺聚乙二醇多巴胺,Dopamine-PEG-Dopamine简介,多巴胺是具有正性肌力活动的单胺化合物

产品名称&#xff1a;多巴胺聚乙二醇多巴胺&#xff0c;双多巴胺聚乙二醇&#xff08;Dopamine-PEG-Dopamine&#xff09; 中文别名&#xff1a;多巴胺PEG多巴胺&#xff0c;双多巴胺聚乙二醇 英文名称&#xff1a;Dopamine-PEG-Dopamine 存储条件&#xff1a;-20C&#xff0…

磨金石教育影视技能干货分享|浅析中国CG特效的发展现状

中国CG特效的发展2015年是一个分水岭。在2015年之前&#xff0c;中国CG 特效发展是混乱的&#xff0c;不成熟的。总体表现就是技术水平不足&#xff0c;缺少人才培养的体系。当时从事CG的公司&#xff0c;大概有两个类型&#xff1a;“技术型与业务型”。所谓技术型的公司&…