Java虚拟机运行时栈的栈帧

目录
  • Java虚拟机栈概述
  • 局部变量表
  • 操作数栈
  • 动态连接
  • 方法的返回地址
  • 结合javap命令理解栈帧

Java虚拟机栈概述

Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:栈帧(Stack Frame)是用于支持Java虚拟机进行方法调用和执行的数据结构,它是虚拟机栈中的栈元素。每个方法在执行的同到都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟栈中从入栈到出栈的过程(说人话就是要执行一个方法,将该方法的栈帧压入栈顶,方法执行完成其栈帧出栈)。在JVM里面,栈帧的操作只有两种:出栈和入栈。正在被线程执行的方法称为当前线程方法,而该方法的栈帧就称为当前帧,执行引擎运行时只对当前栈帧有效。

下面对栈帧的每个组成部分分别介绍一下。

局部变量表

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时就在方法的code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。

局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位,虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小,只是很有导向性地说到每个引都应该能存放一个boolean、byte、char、short,int,float、reference或returnAddress类型的数据,这8种数类都可以使用32位或更小的物理存来存放,但这种描述与明确指出 "每个Slot占用32位长度的内存空间" 是有一些差别的,它运行Slot的长度可以随着处理器、操作系统或虚拟机的不同而发生变化。只要保证使在64位虚拟机中使用了64位的物理内存空间去实现一个Slot,虚拟机仍要使用对齐和补白的手段让Slot在外观上看起来与32位拟机中的一致。

一个Slot可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、float、reference和returnAddress8种类型。第7种reference类表示对一个对象实例的引用,虚拟机规范既没有说明它的长度,也没有明确指出这种引用应有怎样的结构。但是一般来说,虚拟机实现至少都应当能通过这个引用做到两点,一是从此引用直接或间接地查找到对象在Java堆中的数据存放的起始地址索引,二是此引用中直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息。第8种即returnAddress类型目前已经很少见了,现在已经由异常表代替。

对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的引Slot空间。Java语言中明确的(reference类型则可能是32位也可能是64位),64位的数据类型只有long和double两种。虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的Slot数量。如果访问的是32位数据类型的变量,索引 n 就代表了使用第n个Slot,如果是64位数据类型的变量,则说明会同时使用n和n+1两个Slot对于两个相邻的共同存放一个64位数据的两个Slot,不允许采用任何方式单独访问其中的某一个,Java虚拟机规范中明确要求了如果遇到进行这种操作的字节码序列,虚拟机应该在类加载的校验阶段抛出异常。

如果是实例方法(非static的方法),那么局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用"this"。其余参数则按照参数表的顺序来排列,占用从1开始的局部变量Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的Slot(比如方法method(int a1,inta2),参数表为a1和a2,则局部变量表索引0、1、2则分别存储了this指针、a1、a2,如果方法内部有其他内部变量,则在局部变量表中存在a2之后的位置)。

为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。

局部变量不像的类成员变量那样存在"准备阶段"。我们知道类变量有两次赋初始值的过程,一次在准备阶段,赋予系统初始值;另外一次在初始化阶段,赋予程序员定义的初始值。因此,即使在初始化阶段程序员没有为类变量赋值也没有关系,类变量仍然具有一个确定的初始值。但局部变量就不一样,如果一个局部变量定义了但没有赋初始值是不能使用的,不要认为Java中任何情况下都存在诸如整型变量默认为0,布尔型变量默认为false等这样的默认值。

操作数栈

操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出(Last In First out,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在maxstacks数据项中设定的最大值。

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。举个例子,整数加法的字节码指令iadd在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈。

Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。如果当前线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。

Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

Math math=new Math();
math.compute();//调用实例方法compute()

以上面两行代码为例,解释一下动态连接:math.compute()调用时compute()叫符号,需要通过compute()这个符号去到常量池中去找到对应方法的符号引用,运行时将通过符号引用找到方法的字节码指令的内存地址。

方法的返回地址

当一个方法开始执行后,只有两种方式可以退出这个方法。第一种方式是执行引擎遇任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的。

无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整pc计数器的值以指向方法调用指令后面的一条指令等。

结合javap命令理解栈帧

上面进行了大段的文文字介绍,还是不太好理解,下面我们通过javap命令来分析一下方法中的操作指令、局部变量表、操作数栈等。

javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。下面是其用法说明:

D:\wyonline\myworkspaces\framework\Test\bin\com\wkp\jvm>javap
用法: javap <options> <classes>
其中, 可能的选项包括:
  -help  --help  -?        输出此用法消息
  -version                 版本信息
  -v  -verbose             输出附加信息
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置

下面我们写一个简单的Java程序:

package com.wkp.jvm;

public class Math {

	public static final Integer CONSTANT=666;

	public int compute() {//一个方法对应一块栈帧内存区域
		int a=3;
		int b=5;
		int c=(a+b)*10;
		return c;
	}

	public static void main(String[] args) {
		Math math=new Math();
		math.compute();
	}
}

然后进入到Math.class所在目录执行: javap -c Math.class > Math.txt 命令,将Math.class字节码文件反汇编然后输出到Math.txt文件中:

然后我们查看Math.txt的内容如下:我们重点分析下compute方法内的指令,其内部的指令后面我加了注释(这里我是参考上一节的《JVM字节码指令集大全及其介绍》,感兴趣的话可以看一看),注释中的栈就是指的栈帧中的操作数栈,本地变量表就是指的局部变量表。

Compiled from "Math.java"
public class com.wkp.jvm.Math {
  public static final java.lang.Integer CONSTANT;

  static {};
    Code:
       0: sipush        666
       3: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       6: putstatic     #16                 // Field CONSTANT:Ljava/lang/Integer;
       9: return

  public com.wkp.jvm.Math();
    Code:
       0: aload_0
       1: invokespecial #21                 // Method java/lang/Object."<init>":()V
       4: return

  public int compute();
    Code:
       0: iconst_3			//将int类型的3推送至栈顶
       1: istore_1			//将栈顶int类型数值(上面的3)出栈并存入第二个本地变量
       2: iconst_5			//将int类型的5推送至栈顶
       3: istore_2			//将栈顶int类型数值(上面的5)出栈并存入第三个本地变量
       4: iload_1			//将第二个int型本地变量(上面的3)推送至栈顶
       5: iload_2			//将第三个int型本地变量(上面的5)推送至栈顶
       6: iadd				//将栈顶两int型数值出栈,然后相加并将结果压入栈顶
       7: bipush        10	        //将常量值10推送至栈顶
       9: imul				//将栈顶两int型数值出栈,然后相乘并将结果压入栈顶
      10: istore_3			//将栈顶int类型数值(上面的乘积)出栈并存入第四个本地变量
      11: iload_3			//将第四个int类型本地变量推送至栈顶
      12: ireturn			//从当前方法返回int类型值

  public static void main(java.lang.String[]);
    Code:
       0: new           #1                  // class com/wkp/jvm/Math
       3: dup
       4: invokespecial #33                 // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #34                 // Method compute:()I
      12: pop
      13: return
}

下面我们通过图示简单表示一下上面compute方法中指令操作时关于本地变量表、操作数栈的情况:

我们先看下第一行 0: iconst_3 //将int类型的3推送至栈顶,可以看到下图3已经被入栈到操作数栈的栈顶。

我们再看下第二行 1: istore_1 //将栈顶int类型数值(上面的3)出栈并存入第二个本地变量,将上图中栈顶的3出栈然后存入本地表中第二个位置,如下图所示:

第三行、第四行跟上面的一二行指令类似,第四行指令执行后变成如下所示:

第五行、第六行中 4: iload_1 //将第二个int型本地变量(上面的3)推送至栈顶; 5: iload_2 //将第三个int型本地变量(上面的5)推送至栈顶,即将局部变量表中的3和5依次压入栈顶,如下图所示:

然后第七行执行iadd操作,将栈顶的两个int类型数据5和3出栈相加,将得到的和压入栈顶,得到如下结果:

后面的指令操作过程与上面类似,执行完第12行的iload_3指令之后,会得到如下图所示:

关于局部变量表的信息,还可以通过javap -l 命令查看如下图所示,另外还可以通过Idea中的jclasslib 查看。

LocalVariableTable表示的就是局部变量表的信息:

public int compute();
    LineNumberTable:
      line 8: 0
      line 9: 2
      line 10: 4
      line 11: 11
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      13     0  this   Lcom/wkp/jvm/Math;
          2      11     1     a   I
          4       9     2     b   I
         11       2     3     c   I

我们还可以通过 javap -c Math.class > Math.txt 查看更多的信息如下:我们可以看到 9: invokevirtual #34 // Method compute:() 中的 #34 可以在常量池中找到 #34 = Methodref #1.#35 // com/wkp/jvm/Math.compute:()也就是方法的符号引用,运行时通过符号引用解析出来方法的执行指令的内存地址,这个其实就是动态连接。

Classfile /D:/wyonline/myworkspaces/framework/Test/bin/com/wkp/jvm/Math.class
  Last modified 2019-8-24; size 761 bytes
  MD5 checksum be0cdf4bcd037929d3fe0af86d44a837
  Compiled from "Math.java"
public class com.wkp.jvm.Math
  minor version: 0
  major version: 52		//魔数
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:		//常量池
   #1 = Class              #2             // com/wkp/jvm/Math
   #2 = Utf8               com/wkp/jvm/Math
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               CONSTANT
   #6 = Utf8               Ljava/lang/Integer;
   #7 = Utf8               <clinit>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Methodref          #11.#13        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  #11 = Class              #12            // java/lang/Integer
  #12 = Utf8               java/lang/Integer
  #13 = NameAndType        #14:#15        // valueOf:(I)Ljava/lang/Integer;
  #14 = Utf8               valueOf
  #15 = Utf8               (I)Ljava/lang/Integer;
  #16 = Fieldref           #1.#17         // com/wkp/jvm/Math.CONSTANT:Ljava/lang/Integer;
  #17 = NameAndType        #5:#6          // CONSTANT:Ljava/lang/Integer;
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               <init>
  #21 = Methodref          #3.#22         // java/lang/Object."<init>":()V
  #22 = NameAndType        #20:#8         // "<init>":()V
  #23 = Utf8               this
  #24 = Utf8               Lcom/wkp/jvm/Math;
  #25 = Utf8               compute
  #26 = Utf8               ()I
  #27 = Utf8               a
  #28 = Utf8               I
  #29 = Utf8               b
  #30 = Utf8               c
  #31 = Utf8               main
  #32 = Utf8               ([Ljava/lang/String;)V
  #33 = Methodref          #1.#22         // com/wkp/jvm/Math."<init>":()V
  #34 = Methodref          #1.#35         // com/wkp/jvm/Math.compute:()I
  #35 = NameAndType        #25:#26        // compute:()I
  #36 = Utf8               args
  #37 = Utf8               [Ljava/lang/String;
  #38 = Utf8               math
  #39 = Utf8               SourceFile
  #40 = Utf8               Math.java
{
  public static final java.lang.Integer CONSTANT;
    descriptor: Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: sipush        666
         3: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         6: putstatic     #16                 // Field CONSTANT:Ljava/lang/Integer;
         9: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public com.wkp.jvm.Math();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #21                 // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/wkp/jvm/Math;

  public int compute();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_3
         1: istore_1
         2: iconst_5
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: bipush        10
         9: imul
        10: istore_3
        11: iload_3
        12: ireturn
      LineNumberTable:
        line 8: 0
        line 9: 2
        line 10: 4
        line 11: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      13     0  this   Lcom/wkp/jvm/Math;
            2      11     1     a   I
            4       9     2     b   I
           11       2     3     c   I

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #1                  // class com/wkp/jvm/Math
         3: dup
         4: invokespecial #33                 // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #34                 // Method compute:()I
        12: pop
        13: return
      LineNumberTable:
        line 15: 0
        line 16: 8
        line 17: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  args   [Ljava/lang/String;
            8       6     1  math   Lcom/wkp/jvm/Math;
}
SourceFile: "Math.java"

参考:《深入理解Java虚拟机第二版》、《Java虚拟机规范 JavaSE8版》

到此这篇关于Java虚拟机运行时栈的栈帧的文章就介绍到这了,更多相关Java 虚拟机 栈帧内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java虚拟机JVM类加载机制(从类文件到虚拟机)

    一.类加载机制简介 什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口. 类加载机制:所谓的类加载机制就是虚拟机将class文件加载到内存,并对数据进行验证,转换解析和初始化,形成虚拟机可以直接使用的jav

  • java内存模型jvm虚拟机简要分析

    目录 主内存和工作内存 内存间的交互操作 原子性.可见性.有序性 原子性 可见性 有序性 主内存和工作内存 Java 内存模型规定了所有的变量都存储在主内存中, 每条线程有自己的工作内存 线程的工作内存中保存了被该线程使用的变量的主内存副本, 线程对变量的所有操作 (读取.赋值等) 都必须在工作内存中进行, 而不能直接读写主内存中的数据 不同的线程之间也无法直接访问对方工作内存中的变量, 线程间变量值的传递均需要通过主内存来完成 内存间的交互操作 原子性.可见性.有序性 Java 内存模型是围绕

  • 概述java虚拟机中类的加载器及类加载过程

    1. 类加载子系统 1.1 概述 类加载子系统负责从文件系统或者网络中加载Class文件,Class文件在文件开头有特定的文件标识 ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine 决定 加载的类信息存放于一块成为 :方法区的内存空间,除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射) 字节码中的常量池加载到 方法区 -----> 运行时常量池信息 1

  • java虚拟机之JVM调优详解

    JVM常用命令行参数 1. 查看参数列表 虚拟机参数分为基本和扩展两类,在命令行中输入 JAVA_HOME\bin\java就可得到基本参数列表. 在命令行输入 JAVA_HOME\bin\java –X就可得到扩展参数列表. 2. 基本参数说明: -client,-server: 两种Java虚拟机启动方式,client模式启动比较快,但是性能和内存管理相对较差,server模式启动比较慢,但是运行性能比较高,windos上采用的是client模式,Linux采用server模式 -class

  • java虚拟机JVM类加载机制原理(面试必问)

    目录 1.类加载的过程. 1)加载 2)验证 3)准备 4)解析 5)初始化 2.Java 虚拟机中有哪些类加载器? 1)启动类加载器(Bootstrap ClassLoader): 2)扩展类加载器(Extension ClassLoader): 3)应用程序类加载器(Application ClassLoader): 3.什么是双亲委派模型? 4.为什么使用双亲委派模式? 5.有哪些场景破坏了双亲委派模型? 1)线程上下文类加载器 2)Tomcat 的多 Web 应用程序 3)OSGI 实现

  • Java虚拟机运行时栈的栈帧

    目录 Java虚拟机栈概述 局部变量表 操作数栈 动态连接 方法的返回地址 结合javap命令理解栈帧 Java虚拟机栈概述 Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同.虚拟机栈描述的是Java方法执行的内存模型:栈帧(Stack Frame)是用于支持Java虚拟机进行方法调用和执行的数据结构,它是虚拟机栈中的栈元素.每个方法在执行的同到都会创建一个栈帧用于存储局部变量表.操作数栈.动态链接.方法出口等信息. 在编译程序代码的

  • java虚拟机运行时数据区分析

    JVMmemorymodel 这篇文章主要介绍在JVM规范中描述的运行时数据区(RuntimeDataAreas).这些区域设计用来存储被JVM自身或者在JVM上运行的程序所是用的数据. 我们先总览JVM,然后介绍下字节码,最后介绍不同的数据区域. 总览 JVM作为操作系统的抽象,保证同样的代码在不同的硬件或操作系统上的行为一致. 比如: 对于基本类型int,无论在16位/32位/64位操作系统上,都是一个32位有符号整数.范围从-2^31到2^31-1 无论操作系统或者硬件是大字节序还是小字节

  • Java虚拟机运行时数据区域汇总

    程序计数器(Program Counter) 程序计数器作为一个概念模型,这个是用来指示下一条需要执行的字节码指令在哪. Java的多线程实际上是通过线程轮转做到的,如果是一个单核的机器(或单cpu),严格意义上在一个时间块中只会有一个线程在执行.为了线程切换以后能恢复到正确的执行位置,每个线程都需要有一个单独的计数器,每个计数器之间要是独立的互不干扰. 如果线程执行的是Java方法,那么PC指向的是正在执行的虚拟机字节码指令的区域,如果执行的是native方法,那么它是undefined. J

  • Java JVM运行时数据区(Run-Time Data Areas)

    1.官网概括 引用官网说法: The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits.

  • Java异常处理运行时异常(RuntimeException)详解及实例

      Java异常处理运行时异常(RuntimeException)详解及实例 RuntimeException RunntimeException的子类: ClassCastException 多态中,可以使用Instanceof 判断,进行规避 ArithmeticException 进行if判断,如果除数为0,进行return NullPointerException 进行if判断,是否为null ArrayIndexOutOfBoundsException 使用数组length属性,避免越

  • java程序运行时内存分配详解

    一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈.进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享.Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量.虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说

  • JAVA JVM运行时数据区详解

    目录 一.前言 二.运行时数据区整体概架构 三.程序计数器 四.虚拟机栈 1.栈的特点 2.栈帧的内部结构 3.局部变量表 4.操作数栈 5.动态链接 6.方法返回地址 五.本地方法栈 六.堆 1.设置堆大小的参数 2.对象分配过程 3.堆中的GC 4.内存分配策略 5.什么是TLAB 6.堆是分配对象存储的唯一选择吗? 七.方法区 1.方法区概述 2.设置方法区内存大小 3.如何解决OOM问题? 4.方法区存储什么 5.方法区的演进细节 6.方法区的GC 总结 一.前言 这是JVM系列文章的第

  • Java在运行时识别类型信息的方法详解

    前言 在日常的学习工作当中,有一些知识是我们在读书的时候就能够习得:但有一些知识不是的,需要在实践的时候才能得到真知--这或许就是王阳明提倡的"知行合一". 在Java中,并不是所有的类型信息都能在编译阶段明确,有一些类型信息需要在运行时才能确定,这种机制被称为RTTI,英文全称为Run-Time Type Identification,即运行时类型识别,有没有一点"知行合一"的味道?运行时类型识别主要由Class类实现. 01 Class类 在Java中,我们常用

  • java在运行时能修改工作目录吗

    前言 首先我们直接来看一个例子: 程序启动目录即工作目录是/Users/qkkcoolmax/work-private/testuserdir/hehe public static void main(String[] args) { File file = new File("haha/test.log"); System.out.println(file.getAbsolutePath()); //Users/qkkcoolmax/work-private/testuserdir/

  • Java获取视频时长及截取帧截图详解

    前言 只是最近碰到有这方面的项目需求,所以简单 Mark 下本文.下面的示例是参考过他人分享的文章,之后本人再自行实践.调整和测试过的,希望对有这方面需求的人有所帮助. 示例 添加依赖 <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> <version>1.4.4</version> </depe

随机推荐