做网站就上微赞网,做营销网站建设挣钱吗,个性化建网站定制,巨人网络公司简介此章把运行时数据区里比较少的地方讲一下。虚拟机栈#xff0c;堆#xff0c;方法区这些地方后续再讲。 转载https://gitee.com/youthlql/JavaYouth/tree/main/docs/JVM。 运行时数据区概述及线程
前言
本节主要讲的是运行时数据区#xff0c;也就是下图这部分#xff0c… 此章把运行时数据区里比较少的地方讲一下。虚拟机栈堆方法区这些地方后续再讲。 转载https://gitee.com/youthlql/JavaYouth/tree/main/docs/JVM。 运行时数据区概述及线程
前言
本节主要讲的是运行时数据区也就是下图这部分它是在类加载完成后的阶段 当我们通过前面的类的加载 -- 验证 -- 准备 -- 解析 -- 初始化这几个阶段完成后就会用到执行引擎对我们的类进行使用同时执行引擎将会使用到我们运行时数据区加载 -- 链接– 初始化
类比一下也就是大厨做饭我们把大厨后面的东西切好的菜刀调料比作是运行时数据区。而厨师可以类比于执行引擎将通过准备的东西进行制作成精美的菜品。 运行时数据区结构
运行时数据区与内存 内存是非常重要的系统资源是硬盘和CPU的中间仓库及桥梁承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略保证了JVM的高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着部分差异。结合JVM虚拟机规范来探讨一下经典的JVM内存布局。 我们通过磁盘或者网络IO得到的数据都需要先加载到内存中然后CPU从内存中获取数据进行读取也就是说内存充当了CPU和磁盘之间的桥梁 下图来自阿里巴巴手册JDK8 线程的内存空间 Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区其中有一些会随着虚拟机启动而创建随着虚拟机退出而销毁。另外一些则是与线程一一对应的这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。 灰色的为单独线程私有的红色的为多个线程共享的。即 线程独有独立包括程序计数器、栈、本地方法栈线程间共享堆、堆外内存永久代或元空间、代码缓存
Runtime类
每个JVM只有一个Runtime实例。即为运行时环境相当于内存结构的中间的那个框框运行时环境。 线程
JVM 线程
线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行在Hotspot JVM里每个线程都与操作系统的本地线程直接映射 当一个Java线程准备好执行以后此时一个操作系统的本地线程也同时创建。Java线程执行终止后本地线程也会回收 操作系统负责将线程安排调度到任何一个可用的CPU上。一旦本地线程初始化成功它就会调用Java线程中的run()方法 关于线程并发可以看笔者的Java并发系列 JVM 系统线程 如果你使用jconsole或者是任何一个调试工具都能看到在后台有许多线程在运行。这些后台线程不包括调用public static void main(String[])的main线程以及所有这个main线程自己创建的线程。 这些主要的后台系统线程在Hotspot JVM里主要是以下几个
虚拟机线程这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点这样堆才不会变化。这种线程的执行类型括stop-the-world的垃圾收集线程栈收集线程挂起以及偏向锁撤销周期任务线程这种线程是时间周期事件的体现比如中断他们一般用于周期性操作的调度执行GC线程这种线程对在JVM里不同种类的垃圾收集行为提供了支持编译线程这种线程在运行时会将字节码编译成到本地代码信号调度线程这种线程接收信号并发送给JVM在它内部通过调用适当的方法进行处理
程序计数器(PC寄存器)
PC寄存器介绍 官方文档网址https://docs.oracle.com/javase/specs/jvms/se8/html/index.html JVM中的程序计数寄存器Program Counter Register中Register的命名源于CPU的寄存器寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。这里并非是广义上所指的物理寄存器或许将其翻译为PC计数器或指令计数器会更加贴切也称为程序钩子并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。它是一块很小的内存空间几乎可以忽略不记。也是运行速度最快的存储区域。在JVM规范中每个线程都有它自己的程序计数器是线程私有的生命周期与线程的生命周期保持一致。任何时间一个线程都只有一个方法在执行也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址或者如果是在执行native方法则是未指定值undefned。它是程序控制流的指示器分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。它是唯一一个在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.classLast modified 2020-11-2; size 675 bytesMD5 checksum 53b3ef104479ec9e9b7ce5319e5881d3Compiled from PCRegisterTest.java
public class com.atguigu.java.PCRegisterTestminor version: 0major version: 52flags: 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: ()Vflags: ACC_PUBLICCode:stack1, locals1, args_size10: aload_01: invokespecial #1 // Method java/lang/Object.init:()V4: returnLineNumberTable:line 7: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/atguigu/java/PCRegisterTest;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack2, locals5, args_size10: bipush 102: istore_13: bipush 205: istore_26: iload_17: iload_28: iadd9: istore_310: ldc #2 // String abc12: astore 414: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;17: iload_118: invokevirtual #4 // Method java/io/PrintStream.println:(I)V21: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;24: iload_325: invokevirtual #4 // Method java/io/PrintStream.println:(I)V28: returnLineNumberTable:line 10: 0line 11: 3line 12: 6line 14: 10line 15: 14line 16: 21line 18: 28LocalVariableTable:Start Length Slot Name Signature0 29 0 args [Ljava/lang/String;3 26 1 i I6 23 2 j I10 19 3 k I14 15 4 s Ljava/lang/String;
}
SourceFile: PCRegisterTest.java左边的数字代表指令地址指令偏移即 PC 寄存器中可能存储的值然后执行引擎读取 PC 寄存器中的值并执行该指令 两个面试题 使用PC寄存器存储字节码指令地址有什么用呢或者问为什么使用 PC 寄存器来记录当前线程的执行地址呢 因为CPU需要不停的切换各个线程这时候切换回来以后就得知道接着从哪开始继续执行 JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令 PC寄存器为什么被设定为私有的 我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法CPU会不停地做任务切换这样必然导致经常中断或恢复如何保证分毫无差呢为了能够准确地记录各个线程正在执行的当前字节码指令地址最好的办法自然是为每一个线程都分配一个PC寄存器这样一来各个线程之间便可以进行独立计算从而不会出现相互干扰的情况。 由于CPU时间片轮限制众多线程在并发执行过程中任何一个确定的时刻一个处理器或者多核处理器中的一个内核只会执行某个线程中的一条指令。 这样必然导致经常中断或恢复如何保证分毫无差呢每个线程在创建后都会产生自己的程序计数器和栈帧程序计数器在各个线程之间互不影响。 注意并行和并发的区别笔者的并发系列有讲 CPU 时间片 CPU时间片即CPU分配给各个程序的时间每个线程被分配一个时间段称作它的时间片。 在宏观上我们可以同时打开多个应用程序每个程序并行不悖同时运行。 但在微观上由于只有一个CPU一次只能处理程序要求的一部分如何处理公平一种方法就是引入时间片每个程序轮流执行。
本地方法接口
本地方法 简单地讲一个Native Method是一个Java调用非Java代码的接囗一个Native Method是这样一个Java方法该方法的实现由非Java语言实现比如C。这个特征并非Java所特有很多其它的编程语言都有这一机制比如在C中你可以用extern 告知C编译器去调用一个C的函数。“A native method is a Java method whose implementation is provided by non-java code.”本地方法是一个非Java的方法它的具体实现是非Java代码的实现在定义一个native method时并不提供实现体有些像定义一个Java interface因为其实现体是由非java语言在外面实现的。本地接口的作用是融合不同的编程语言为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应用之外的繁琐的细节。
与操作系统的交互
JVM支持着Java语言本身和运行时库它是Java程序赖以生存的平台它由一个解释器解释字节码和一些连接到本地代码的库组成。然而不管怎样它毕竟不是一个完整的系统它经常依赖于一底层系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法我们得以用Java实现了jre的与底层系统的交互甚至JVM的一些部分就是用C写的。还有如果我们要使用一些Java语言本身没有提供封装的操作系统的特性时我们也需要使用本地方法。
Sun’s Java
Sun的解释器是用C实现的这使得它能像一些普通的C一样与外部交互。jre大部分是用Java实现的它也通过一些本地方法与外界交互。例如类java.lang.Thread的setPriority()方法是用Java实现的但是它实现调用的是该类里的本地方法setPriority0()。这个本地方法是用C实现的并被植入JVM内部在Windows 95的平台上这个本地方法最终将调用Win32 setpriority() API。这是一个本地方法的具体实现由JVM直接提供更多的情况是本地方法由外部的动态链接库external dynamic link library提供然后被JVM调用。
本地方法的现状
目前该方法使用的越来越少了除非是与硬件有关的应用比如通过Java程序驱动打印机或者Java系统管理生产设备在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达比如可以使用Socket通信也可以使用Web Service等等不多做介绍。
本地方法栈
Java虚拟机栈于管理Java方法的调用而本地方法栈用于管理本地方法的调用。本地方法栈也是线程私有的。允许被实现成固定或者是可动态扩展的内存大小在内存溢出方面和虚拟机栈相同 如果线程请求分配的栈容量超过本地方法栈允许的最大容量Java虚拟机将会抛出一个stackoverflowError 异常。如果本地方法栈可以动态扩展并且在尝试扩展的时候无法申请到足够的内存或者在创建新的线程时没有足够的内存去创建对应的本地方法栈那么Java虚拟机将会抛出一个outofMemoryError异常。 本地方法一般是使用C语言或C语言实现的。它的具体做法是Native Method Stack中登记native方法在Execution Engine 执行时加载本地方法库。 注意事项
当某个线程调用一个本地方法时它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区它甚至可以直接使用本地处理器中的寄存器直接从本地内存的堆中分配任意数量的内存 并不是所有的JVM都支持本地方法。因为Java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。如果JVM产品不打算支持native方法也可以无需实现本地方法栈。在Hotspot JVM中直接将本地方法栈和虚拟机栈合二为一。