JVM内存模型系列

JVM作为运行Java程序的平台,我们Java程序员必须要去了解它。JVM 能涉及非常庞大的一块知识体系, 比如内存结构、 垃圾回收、 类加载、 性能调优、 JVM 自身优化技术、 执行引擎、 类文件结构、 监控工具等。但是在所有的知识体系中, 都或多或少跟内存结构有一定的关系:比如垃圾回收回收的就是内存、 类加载加载到的地方也是内存、 性能优化也涉及到内存优化、 执行引擎与内存密不可分、 类文件结构与内存的设计有关系, 监控工具也会监控内存。 所以内存结构处于 JVM 中核心位置。 也是属于我们入门 JVM 学习的最好的选择。同时 JVM 是一个虚拟化的操作系统, 所以除了要虚拟指令之外, 最重要的一个事情就是需要虚拟化内存, 这个虚拟化内存就是我们马上要讲到的 JVM 的内存区域。

在这里插入图片描述

JVM内存结构可以分为五个模块加上一个直接内存。其中这些模块又可以分为两个大类,线程共享区域和线程私有区域。
线程共享区域:

  • 堆内存:JVM 上最大的内存区域, 我们申请的几乎所有的对象, 都是在这里存储的。
  • 方法区:JVM的逻辑划分,不同版本有不同实现。主要是用来存放已被虚拟机加载的类相关信息, 包括类信息、 静态变量、 常量、 运行时常量池、 字符串常量池等。

线程私有区域:

  • 虚拟机栈:JVM 运行过程中存储当前线程运行方法所需的数据, 指令、 返回地址。
  • 本地方法栈:Java程序调用底层C/C++函数库。
  • 程序计数器:当前线程执行的字节码的行号指示器。

直接内存:又叫堆外内存,它不是虚拟机运行时数据区的一部分,但是虚拟机部分逻辑会用到直接内存。

我们就从线程私有的区域开始讲起吧!

虚拟机栈

它的数据结构如其名,栈是一种FILO(先进后出)的数据结构,它的声明周期和线程息息相关,它的作用就是存储当前线程运行java方法所需的数据、指令、返回地址。(虚拟机栈在Java程序员口中简称,为了方便下文就简称栈)
JDK1.8官方指定栈的默认大小为1M,程序运行时可以用 -Xss命令指定大小
栈的结构:
[图片上传失败...(image-df378e-1595071280290)]

栈帧:在一个线程里,每当调用一个方法就会创建一个栈帧,并入栈,当方法执行完以后进行出栈

例如:在java代码中A()方法中调用了B()方法,B()方法中又调用了C()方法。那么线程执行A()时,创建一个栈帧入栈,执行B()方法时又创建一个栈帧入栈.....,当C()方法执行完毕出栈,紧跟着B()结束也跟着出栈,直至栈底的栈帧出栈宣告完毕,此时线程也跟着消亡。
栈的组成元素时栈帧,那么栈帧里面长什么样子呢?我们刚说到,栈是拿来存数据, 指令、 返回地址的,那么这些东西肯定是存在栈帧中了,我们来看看栈帧的内部结构:

  • 局部变量表:顾名思义它是一张表来存数据的,而且是局部变量的数据。局部变量表中存储的数据是一个32位长度的数据,比如我们常见的8大基本类型变量,如果是double和long则使用32位高低位来标识,如果是对象,那么就存储对象的堆内存地址。
  • 操作数栈:顾名思义它的内存结构也是一个栈结构(先进后出),它的作用就是存储方法运行时执行引擎需要计算的数据。
  • 动态链接:解决符号引用相关问题(后续类加载机制时解析)。
  • 返回地址:方法执行完毕需要将程序计数器中的地址作为返回,便于后续栈帧执行。

光说概念是不是很枯燥,我们写段代码,通过反汇编来瞅瞅

/**
 * @author Minor
 */
public class Demo1 {

    public int test() {
        int a = 10;
        int b = 20;
        int c = (a+b)*2;
        return c;
    }

}

首先我们定义了一个非常普通的java方法test(),内部定义两个变量a,b然后计算他们的和再乘以2,赋值给c然后返回。我们先用javac命令编译一下得到Demo1.class文件,然后用javap -c命令查看这个class文件的反汇编指令代码,
为了方便,我直接在反汇编指令里面写注释:

 wangzhi@wangzhideMacBook-Pro   ~/Desktop/JavaBase/src/com/company/base  javap -c Demo1   
警告: 二进制文件Demo1包含com.company.base.Demo1
Compiled from "Demo1.java"
public class com.company.base.Demo1 {
  public com.company.base.Demo1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int test();                    // 我们java代码里定义的test()方法
    Code:                               // 字节码指令
       0: bipush        10              // 将常量10压入操作数栈
       2: istore_1                      // 将操作数栈的值10存储到局部变量表下标为1的位置
       3: bipush        20              // 将常量20压入操作数栈
       5: istore_2                      // 将操作数栈的值10存储到局部变量表下标为2的位置
       6: iload_1                       // 将局部变量表下标为1的变量压入操作数栈      
       7: iload_2                       // 将局部变量表下标为2的变量压入操作数栈  
       8: iadd                          // 加法运算,将操作数栈里的值进行求和
       9: iconst_2                      // 将值为2的常量压入操作数栈
      10: imul                          // 乘法运算
      11: istore_3                      // 将操作数栈的值存储到局部变量表下标为3的位置
      12: iload_3                       // 将局部变量表下标为3的变量压入操作数栈
      13: ireturn                       // 方法返回
}

class指令集参考表:[https://cloud.tencent.com/developer/article/1333540]
(https://cloud.tencent.com/developer/article/1333540)
我们可以看到,一个简单的方法,解释成jvm指令时变得很复杂,但是每一步缺逻辑清晰。注意一点istore_n指令表示将操作数栈里的值存入局部变量表的下标n的位置。iconst_n表示将n常量压入操作数栈,常量是几,n就是几。
细心的小伙伴注意到了,当操作数栈存储常量10的时候,为什么存储的是局部变量表下标为1的位置。其实java代码底层对方法的调用有一个this,代表当前对象,所以局部变量表index[0]号位置是当前对象this的引用。

有一点需要注意的是,栈这个数据结构是先入后出,如果一个线程不断地入栈而没有出栈,就会造成栈溢出错误StackOverflowException,比如递归操作控制不当就会发生异常。

程序计数器

程序计数器是用来记录线程执行字节码的行号地址,因为现代计算机的工作模式基于CPU的时间片轮转机制,线程在执行程序的时候难免会遇到CPU调度问题,此时就需要一个地方来存储线程当前执行的位置。显然,每个线程各自独立,都有属于自己一份的程序计数器。由于结构简单,功能单一,程序计数器也是JVM内存模型中唯一不会发生内存溢出的地方。需要值得注意的点是当java线程在执行本地方法(native修饰的方法)时,程序计数器并不会记录执行位置,因为操作系统层面也有一个程序计数器,本地方法依靠它去记录。

本地方法栈

本地方发栈顾名思义也是一个栈结构,和虚拟机栈类似。虚拟机栈用于控制java方法的调用,而本地方法栈控制本地方法的调用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348