jvm内存结构

1 jvm结构

jvm结构

  1. 一个类会通过jvm的第一个组成部分类装载子系统(C++实现),会把字节码文件丢到jvm的内存区域
  2. 最终通过jvm的另一个组成部分字节码执行引擎执行内存区域的代码

我们常说的jvm的内存模型说的就是 jvm的内存区域的划分,堆,栈,方法区,程序计数器这些玩意。

另外,常说的 运行时常量池是方法区的一部分

2 jvm的内存区域

2.1 栈

2.1.1 线程栈

栈(线程栈),每当一个线程开始运行的时候,就会在整个栈空间开辟属于这个线程独立的线程栈(栈空间)

比如当主线程执行的时候就会开辟一个主线程栈。

在这里插入图片描述

用于存放每个线程执行中的局部变量,也就是说每个线程之间的隔离的

2.1.2 栈帧(方法栈帧)

线程栈是属于每个执行的线程的内存空间,而方法栈帧就是执行每个方法会有自己的内存空间,这块空间就是方法栈帧。

栈帧又分了局部变量表,操作数栈,动态链接,方法出口

在这里插入图片描述

当方法执行完毕,就会把方法这块的栈帧空间释放掉

这里就可以理解为为何这部空间采用的栈这种先进后出的模型了

就拿上面如图中的代码来说,先执行main方法,就会开辟mian方法的栈帧空间并压入main线程栈,紧接着执行compute方法,就会开辟compute方法的栈帧空间并压入main线程栈(此时main方法还未执行结束),当compute执行结束,此时就会释放compute方法的栈帧空间(出栈),当main方法执行结束,此时就会释放main方法的栈帧空间(出栈),可见方法的调用和栈结构的先进后出模型是十分吻合的

2.1.3 字节码文件(局部变量表,操作数栈,程序计数器 )

栈帧又分了局部变量表,操作数栈,动态链接,方法出口;

为了说明这几部分,通过字节码码文件,来说明:

package study.wyy.jvm.classLoader;

import study.wyy.jvm.model.User;

public class Math {

    public static User user = new User();

    public static final int initData = 666;

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

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

这段代码对应的字节码文件(Math.class):

cafe babe 0000 0034 0041 0a00 0e00 2b0a
000e 002c 0a00 2d00 2e08 002f 0a00 3000
3107 0032 0a00 0600 2b0a 0006 0033 0700
340a 0009 002b 0700 350a 000b 002b 0900
0600 3607 0037 0100 0475 7365 7201 001a
4c73 7475 6479 2f77 7979 2f6a 766d 2f6d
6f64 656c 2f55 7365 723b 0100 0869 6e69
7444 6174 6101 0001 4901 000d 436f 6e73
7461 6e74 5661 6c75 6503 0000 029a 0100
063c 696e 6974 3e01 0003 2829 5601 0004
436f 6465 0100 0f4c 696e 654e 756d 6265
7254 6162 6c65 0100 124c 6f63 616c 5661
7269 6162 6c65 5461 626c 6501 0004 7468

。。。。

这个的可读性是很差的,但是可以看到文件开始的cafe babe,这个就是java字节码文件的标识

可以通过javap 命令让这个文件变得可读性高一点,javap可接收的参数:

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

就使用这个命令查看一下,这里将结果输出到了桌面的Math.txt

 javap -c Math.class > /Users/wyaoyao/Desktop/Math.txt

比如compute方法对应的指令为


 public int compute();
   Code:
      0: iconst_1
      1: istore_1
      2: iconst_2
      3: istore_2
      4: iload_1
      5: iload_2
      6: iadd
      7: bipush        10
      9: imul
     10: istore_3
     11: iload_3
     12: ireturn

就针对这些些指令,说明栈帧中的局部变量表,操作数栈,动态链接,方法出口;

iconst_1:将int类型常量1压入操作数栈

在这里插入图片描述

istore_1:将int类型值存入局部变量1,在这里就是把a这个变量放入局部变量表,并把刚刚放到操作数栈的的1出栈放到a在局部变量表的位置

这里的局部变量1的1可以理解为局部变量表的索引,索引为0的地方存的就是我们的this变量(jvm默认会把this放到局部变量0)

可见这两行指令对应的代码就是:

int a = 1

在这里插入图片描述

接下来的:

iconst_2
istore_2

和刚刚的一样,就不做分析了,执行完这两个指令后:

在这里插入图片描述

接下来就是iload_1, 在这之前,需要先了解程序计数器是干嘛的

程序计数器使用存程序正在运行或者将要运行哪行代码的内存位置,有的地方叫行号;

刚刚每个指令前面都有一个数字,就可以把这些数字理解为代码位置的标识,程序计数器就是用来记录代码执行到的位置

Code:
       0: iconst_1
       1: istore_1
       2: iconst_2

比如现在程序计数器里面保存当前执行的位置就是4所在的这行代码的位置

每执行完一行代码,字节码执行引擎都会去修改程序计数器

为啥要有程序计数器,很简单多线程的时候,发生线程执行权切换的时候,需要记录下当前线程执行到哪里,等着这个线程再度抢到执行权的时候,就可以继续执行了

在这里插入图片描述

接下来看iload_1和iload_2,分别是从局部变量1中装载int类型值和从局部变量2中装载int类型值

就是说会把局部变量1和局部变量2中的int类型的值装载出来,压入操作数栈

在这里插入图片描述

接着就是iadd:执行int类型的加法;
就会把操作数栈中的数出栈,然后就行加法,这里就是1+2,,并把执行的结果压入操作数栈在这里插入图片描述

执行到这里,程序计数器内部就会变成保存7所在代码指令的内存位置

7: bipush  10

bipush:将一个8位带符号整数压入栈

这里就是把10压入操作数栈

在这里插入图片描述

imul: 执行int类型的乘法,和iadd一样

这里就会把操作数栈中的3和10出栈,计算乘法,并将结果压入栈

在这里插入图片描述

istore_3 :将int类型值存入局部变量3

这里就会给变量c在局部变量表开空间(局部变量3),把操作数栈中的30 存入到局部变量表局部变量3这个位置

在这里插入图片描述

iload_3:从局部变量3中装载int类型值,这里就会把30从局部变量表中装载出来,压入操作数栈

ireturn:从方法中返回int类型的数据,这里就是把操作数栈中的30 返回到主线程中

在这里插入图片描述

2.1.4 方法出口

方法出口记录的就是当前方法,执行结束要回到哪里去,

比如这里的compute方法执行结束后就会回到main方法

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

2.2 堆

大家都知道堆是放引用类型变量的;

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

Math math = new Math(); 执行结束后:

在这里插入图片描述

2.3.1 堆的划分

堆的划分:

  1. 年轻代,占三分之一
  2. 老年代,占三分之二

这个比例是可以调整的

年轻代又分为:

  1. eden
  2. survivor

在这里插入图片描述

2.3 本地方法栈

就是本地方法对应的栈,没啥可说的,现在跨语言调用的方式越来越多,很少用本地方法去调用c的代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值