JVM

JVM自动内存管理之一

2019-01-15  本文已影响66人  AlanKim

首先看个大图:

874710-20161206164443851-339965653.png

程序计数器:

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

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

栈帧:

  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可重用
栈实战

基本源码如下:

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相关,下面详细说明下:

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

此时:

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

此时:

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

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

第三步

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

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

此时:

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

此时:

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

此时:

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

此时:

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

猜你喜欢

热点阅读