JVM自动内存管理之一

首先看个大图:

2143704-f877f0fa9031478f.png
874710-20161206164443851-339965653.png

程序计数器:

  • 一块很小的内存空间,可以理解为当前线程所执行字节码的行号指针。
  • 如果执行的是java方法,则计数器记录的是正在执行的字节码指令的内存地址;
  • 如果是执行native方法,那么此值为空。
  • 程序计数器是唯一一个在JVM规范中,没有规定任何OutOfMemoryError(即内存溢出)情况的区域。

java虚拟机栈 — 服务于java字节码方法

  • 线程私有
  • 后进先出栈,LIFO
  • 存储栈帧,支持java方法的调用、执行和退出
  • 可能会出现OutOfMemoryError异常和StackOverFlowError异常

本地方法栈 — 服务于本地native方法

  • 线程私有
  • LIFO
  • 支持native方法的调用、执行和退出
  • 可能会出现OutOfMemoryError异常和StackOverFlowError异常
  • hotspot 把java虚拟机栈和本地方法栈这两个的实现合二为一了,JVM规范并没有要求虚拟机怎么实现。

栈帧:

  1. 是java虚拟机栈中存储的内容

  2. 用于存储数据和部分过程结果

  3. 以及处理动态链接、方法返回值和异常分派

  4. hotspot虚拟机实现中,-Xss128K 用于控制分配给线程的栈空间,其实也就是java虚拟机栈+本地方法栈,-XSS跟堆空间没关系。

  5. 一个完整的栈帧包括:

    • 局部变量表
      • 变量值的存储空间,方法参数、局部变量
 - 长度在编译期就确定了
 - 由若干个slot组成,每个slot都应该能存放boolean byte char short float 以及 reference returnAddress数据
 - 两个slot可以存储long 和 double数据,64位。类似JMM中long double的实现,不过由于是线程私有,所以不会有线程安全信息。
 - reference:对象实例的引用,获取实例内存地址,以及方法区中对应的实例类型class信息
 - returnAddress,基本上不用了。
 - 用于方法之间存放参数,以及在方法执行过程中,存储技术数据类型的值+对象的引用。
 - 如果是一个成员方法,那么在第0个slot,存放的是当前方法所属对象的reference,用this可以访问到。
 - slot可重用
  • 操作数栈
    • 后进先出,LIFO(Last In,First Out)
    • 代替cpu的寄存器(jvm本身没有寄存器),是指令的工作区,结果会暂存到操作数栈,然后再出栈存入局部变量表。可以认为是计算时临时数据的存储区域。
    • 由若干个Entry组成
    • 单个Entry可以存储一个jvm中定义的任意数据类型的值,包括long和double
    • 但是long和double类型的Entry深度为2,其他类型深度为1
    • 执行过程中,用于存储计算参数和计算结果;方法调用时,用来准备调用方法的参数,以及接收方法返回结果。
  • 动态链接信息
  • 方法正常完成信息—方法返回的结果地址
  • 方法异常完成信息
  • 在编译期就已经确定需要多大的局部变量表,多长的栈深度,写入到class文件中。
栈实战

基本源码如下:

public class Test{

    public int calc(){
        int a = 100;
        int b = 200;
        int c = 300;

        return (a+b) * c;
    }
}

编译并javap:

javac Test.java
javap -verbose Test

结果如下:

Classfile /Users/kinomousakai/middleplatform/Test.class
  Last modified May 4, 2018; size 262 bytes
  MD5 checksum 469e4b9f043da5724726120da5102b22
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 54
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // Test
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // Test
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               calc
   #9 = Utf8               ()I
  #10 = Utf8               SourceFile
  #11 = Utf8               Test.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               Test
  #14 = Utf8               java/lang/Object
{
  public Test();
    descriptor: ()V
    flags: (0x0001) 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 1: 0

  public int calc();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        100
         2: istore_1
         3: sipush        200
         6: istore_2
         7: sipush        300
        10: istore_3
        11: iload_1
        12: iload_2
        13: iadd
        14: iload_3
        15: imul
        16: ireturn
      LineNumberTable:
        line 4: 0
        line 5: 3
        line 6: 7
        line 8: 11
}
SourceFile: "Test.java"

关键看stack相关,下面详细说明下:

初始阶段-第一步:
2143704-b91ec902b894843e.png
WechatIMG249.png

此时:

  1. 程序计数器为0,表示执行第0行
  2. 局部变量表的第0个写入this
  3. 操作栈会通过指令bipush,将后面的100 写入操作栈顶
第二步
2143704-64cad2cdacd8b74e.png
WechatIMG250.png

此时:

  1. 程序计数器变为2,因为第0行有两个数据,bipush和100,占用了两个偏移量。
  2. istore_1 将操作数栈顶的数据出栈,并存入局部变量表第一个位置。
  3. 偏移量3-10 执行0-2 一样的动作,执行完10之后,内存区域如下:
2143704-2cc041ecffa15dde.png
WechatIMG251.png

忽略操作数栈顶的100,以及程序计数器的11,这是第11个指令的作用。

第三步

接上一步的图,第11步做了如下操作:

  1. 程序计数器变为11,表示当前执行第11位地址偏移量对应的指令
  2. iload_1表示将 局部变量表Slot=1的数据,存入到操作数栈顶中
第四步:
2143704-9fe3b2ff0c28c4cb.png
WechatIMG252.png

此时:

  1. 程序计数器变为12
  2. iload_2 表示将局部变量表Slot=2的数据存入操作数栈顶
  3. 此时操作数栈深度变为2,其中数据为,栈顶entry=200,entry栈顶+1 = 100
第五步
2143704-9d7f68a9d8fede53.png
WechatIMG253.png

此时:

  1. 程序计数器变为13
  2. iadd表示将操作数栈中距离栈顶最近的两个元素,出栈,相加,并将结果写入操作数栈顶,此时操作数栈深度变为1
第六步:
2143704-7daf9002bdec359c.png
WechatIMG254.png

此时:

  1. 程序计数器变为14
  2. 将局部变量表中下标为3的元素取出,并压入操作数栈顶,此时操作数栈深度重新变成2
第七步
2143704-2c295c38792ea939.png
WechatIMG255.png

此时:

  1. 操作数栈变为15
  2. imul为整数乘法指令,把操作数栈顶最近的两个元素取出,相乘,并重新压入操作数栈顶
  3. 执行第16位偏移量,程序计数器变为16
  4. ireturn会将操作栈顶的元素出栈,并将其作为返回结果,整个逻辑结束。此时操作栈深度为0

猜你喜欢

转载自blog.csdn.net/weixin_34314962/article/details/87420632
今日推荐