jvm内存模型_3、JVM内存模型虚拟机栈

3.1、虚拟机栈

栈是一种数据结构,先进后出(FILO),可用数据或链表实现。JVM虚拟机栈也是栈数据结构,它是用来解决程序运行问题的,作用就一个,就是JVM在执行java方法时,执行栈帧的不断入栈出栈操作。

e1b9c25d2ee32cb5ec104e65d657acf6.png

虚拟机栈内部结构

特点:

    1、线程私有,每创建一个线程时,就会为线程创建一块独立的虚拟机栈内存放入当前线程,当线程执行结束,空间就被销毁,JVM多线程是通过线程轮流切换来获取执行时间的,当前正在运行的线程我们称为当前线程,每当线程执行一个方法时,就会在它本身的虚拟机栈中压入一个栈帧,我们把栈顶的栈帧称为当前栈帧,即就是正在执行的方法空间;

    2、不存在垃圾回收问题;

    3、规范中有两种异常:StackOverflowError、OutOfMemeryError。

    StackOverflowError:当前线程栈深度大于所请求的JVM所允许的最大深度时,将抛出此异常;

    OutOfMemeryError:当虚拟机栈申请不到足够内存时,将跑除此异常。

3.2 内存概念模型

3.2.1 栈帧

  • 每一个方法执行及运行结束,就会有一个伴随一个栈帧入栈及出栈;

  • 栈帧是虚拟栈中数据的存储单位;

  • 栈帧线程私有的,也就代表方法中的局部变量都是线程安全的;

    d078871e88f35efc607d4839a556d4e4.png

栈帧结构

局部变量表:

  • 存放的变量为GC ROOT,它直接或者间接引用的对象不回收(后续讲解GC机制时会详解);

  • 编译时大小便可知;

  • 用于存放方法中的局部变量,8个基本类型、引用、returnAddress;

  • 以Slot为存在单位,Slot为32位的,long和double类型占用2个Slot。

32f2c77552a5f9589fe5f2599e1c399e.png

如上图局部变量表,索引0处如果为成员方法,存放的是this变量,如果为非成员方法,空着,这也就是为什么在静态方法中无法使用this的原因。

操作数栈:

用于临时存放方法计算中的中间结果; 不像局部变量一样,通过索引存储值,它是通过对栈顶入栈出栈来处理值的; 如果方法有返回值,那么返回值将会压入操作数中,并更新程序计数器中为下一条执行的指令。

操作演示:

下面一步一步来操作下JVM栈的概念模型,开场肯定是煮栗子,再慢慢拨栗子吃:

public class JvmStackTest {

   public static void main(String[] args) {

       int i=1;

       int j=128;

       int c=add(i,j);

       System.out.println(c);

   }

   private static int add(int a,int b){

       return a+b;

   }

}

java有个小工具,javap,我们通过javap -v来查看下上面字节码:

Classfile /D:/newproject/test/target/test-classes/com/snailct/jvm/vmstack/JvmStackTest.class

Last modified 2020-9-17; size 724 bytes

MD5 checksum 281e06089b17ce6b278edcc0ac0eef80

Compiled from "JvmStackTest.java"

public class com.snailct.jvm.vmstack.JvmStackTest

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref          #6.#28         // java/lang/Object."":()V

#2 = Methodref          #5.#29         // com/snailct/jvm/vmstack/JvmStackTest.add:(II)I

#3 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;

#4 = Methodref          #32.#33        // java/io/PrintStream.println:(I)V

#5 = Class              #34            // com/snailct/jvm/vmstack/JvmStackTest

#6 = Class              #35            // java/lang/Object

#7 = Utf8              

#8 = Utf8               ()V

#9 = Utf8               Code

#10 = Utf8               LineNumberTable

#11 = Utf8               LocalVariableTable

#12 = Utf8               this

#13 = Utf8               Lcom/snailct/jvm/vmstack/JvmStackTest;

#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               c

#22 = Utf8               add

#23 = Utf8               (II)I

#24 = Utf8               a

#25 = Utf8               b

#26 = Utf8               SourceFile

#27 = Utf8               JvmStackTest.java

#28 = NameAndType        #7:#8          // "":()V

#29 = NameAndType        #22:#23        // add:(II)I

#30 = Class              #36            // java/lang/System

#31 = NameAndType        #37:#38        // out:Ljava/io/PrintStream;

#32 = Class              #39            // java/io/PrintStream

#33 = NameAndType        #40:#41        // println:(I)V

#34 = Utf8               com/snailct/jvm/vmstack/JvmStackTest

#35 = Utf8               java/lang/Object

#36 = Utf8               java/lang/System

#37 = Utf8               out

#38 = Utf8               Ljava/io/PrintStream;

#39 = Utf8               java/io/PrintStream

#40 = Utf8               println

#41 = Utf8               (I)V

{

public com.snailct.jvm.vmstack.JvmStackTest();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1                  // Method java/lang/Object."":()V

4: return

LineNumberTable:

line 3: 0

LocalVariableTable:

Start  Length  Slot  Name   Signature

0       5     0  this   Lcom/snailct/jvm/vmstack/JvmStackTest;

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=4, args_size=1

0: iconst_1

1: istore_1

2: sipush        128

5: istore_2

6: iload_1

7: iload_2

8: invokestatic  #2                  // Method add:(II)I

11: istore_3

12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;

15: iload_3

16: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V

19: return

LineNumberTable:

line 5: 0

line 6: 2

line 7: 6

line 8: 12

line 9: 19

LocalVariableTable:

Start  Length  Slot  Name   Signature

0      20     0  args   [Ljava/lang/String;

2      18     1     i   I

6      14     2     j   I

12       8     3     c   I

}

从main线程开始运行,在main线程空间中分配栈空间,栈空间中分配栈帧内存

stack=2(操作数占用2个slot), locals=4(局部变量占用4个slot), args_size=1 一个参数。

执行引擎开始执行字节码

 0: iconst_1

iconst指令是把int值压入操作数栈顶。

        内存模型如下图:

c36fda32ee3db08075ea2e7382c7dbd5.png

1 istore_1

把操作数栈顶1个元素弹出压入局部变量。 内存模型如图:

f13acbca60261bd585ab103b394dd2d1.png

2 sipush 128

把int整型数值压入操作数栈:

5850ff42895a631a0d16af332509ff5c.png

后面的指令大家自己操作,在此就不多说。

动态链接:

执行运行时常量池的引用; 每个栈帧内部都有一个指定运行常量池或者方法的引用,主要用于将符号引用转化为直接引用,白话就是多态机制的实现,后续详解

返回地址:

方法返回分为两种:

    1、正常退出,通过return返回值或者void(本质上也会有return);

    2、异常退出,抛出异常。

不管哪种退出,都会返回到方法调用者的位置,这就需要记录程序计数器的值,不过异常退出是通过异常表来记录的,栈帧中不会保留这信息。

3.3 Q&A

1、方法中的哪些变量是线程安全的,哪些不是,为什么?

答:方法中的局部变量是线程安全的,因为方法是由虚拟机栈帧局部变量来存储的,为线程私有,作用域仅仅在方法内部。而参数和返回值变量可能会涉及到方法作用域外的一些操作,所以为非线程安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值