java虚拟机结构图解(堆栈方法区)

2,520 阅读7分钟

*这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

题外链接:jdk1.7和1.8版本的方法区构造变化(马上编写)
到此应该了解了class文件和类加载的过程,也应该听到了一些堆栈方法区等常用名字,下面来跟大家了解下jvm内存布局(1.8)
在这里插入图片描述

jvm内存基础(有一点点基础的即可跳过)

jvm 最大的分类分为两个,一个为线程共享的。一个是线程私有的
线程私有:jvm虚拟机栈,本地方法栈,程序计数器
线程共享:堆和方法区

线程私有:比如我除了main线程还另外写了一个Test线程,他们同时在执行,那么我们内存里就会出现两个线程栈(即图中右边紫色的部分各来一份,全部整到一起,)图中左边即为两个线程栈,每个线程栈里面的都有若干个栈帧,一个方法对应一个栈帧,即可以理解为一个线程里执行了很多方法
线程共享:比如我new了一个对象,此对象放在堆中,我们栈中的局部变量表里面的变量只是引用了这个对象的存放地址,并且其他线程也可以用这个地址,即堆是我们所有线程所共有的

先来简单了解下:
堆就是存放对象的地方比如new Test()
栈是存放变量的地方 比如 方法里的int a
方法区是存放类信息的地方 比如 类的全局变量 int a
接下来详细介绍下每部分的内容

方法区(元空间)

方法区是装载类信息的一个概念,hotspot虚拟机对此方法区的实现是元空间(这是1.8的实现,如需详细转题外链接),当一个类加载器启动时,(类加载器在堆中),然后他会去主机的硬盘上将我们的字节码文件装载进jvm方法区,方法区此时装载了类的信息,堆中的Class对象的引用,和类加载器的引用

在这里插入图片描述
也就是说,类初始化过后,(还没有创建对象),和没初始化此类的区别就是多了个方法区的字节码内存块,和堆中多了个对应类的Class对象,由此可见方法区多么重要
可能有的会对静态变量和字符串常量池的位置迷惑,认为是存放在方法区,其实在1.6时是这样的,现在讲的是1.8,这涉及到jdk版本的变化:
jdk1.7和1.8的方法区构造变化(面试常问)

接下来介绍字节码内存块的具体内容

  1. 类型信息
    该类是public 的? final的?
    该类的全限定名是什么
    他的父类的全限定名
    等这些
    其实就是存了这个类的类信息
  2. 字段信息
    修饰符是什么? private?public?。。。
    字段的类型是什么。。。
    字段名字叫啥。。。
    说白了就是存放了所有字段的所有信息
  3. 方法信息
    修饰符;
    方法返回值;
    方法名;
    局部变量表和操作数栈的大小;
    方法体的字节码;
    异常表
    说白了就是存放了所有方法的所有信息
  4. 运行时常量池
    全局的常量
    方法名。方法描述符,类名,字段名等不会变的东西(之所以叫常量池)
    这里为什么在方法中存放了方法名,这里还要存放?因为常量池的信息是可以被快速找到的内容,它像数组一样通过索引访问,就是专门用来做查找的。把一些常用的信息放在这里可以方便的找到,还有记住常量和变量是完全不同的两个概念,存储方式也不同,可以从上面的结构看出来
  5. 一个到classLoader的引用
    如图
  6. 一个到类Class的引用
    如图

jvm栈

虚拟机栈是用于描述java方法执行的内存模型
结构图
在这里插入图片描述
局部变量表:就是存储我们方法运行时的变量
操作数栈:就是一个用于运算时临时存放数据的空间
动态连接:就是在运行时将符号引用转化为直接引用,还记得吗,Class 文件中存放了大量的符号引用,这些符号引用一部分会在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
返回地址:很简单一个栈桢代表一个方法,方法执行完毕需要返回调用它的地方,这个反回地址就记录了这个地址

很抽象,不要紧,举例说明下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16class Test{     public int count(){         int d = 1;         int e = 2;         int f = d+e;         return f;     }         public static void main(String[] args) {         Test a = new Test();         int k = a.count();     } }

第一步main方法执行,即一个代表main方法的栈帧入栈,此时只有这个线程在执行
在这里插入图片描述
这时,a和k都已经被放到局部变量表里等待初始化(对象的半初始化状态),然后开始创建对象(这个过程讲到堆再讲),创建以后还没给a赋值,先计算下一个要赋的值k:a.count(),
在这里插入图片描述
此时调用了一个方法,那么栈中就会压入入一个count方法的栈帧如图
在这里插入图片描述
然后开始执行上面那个count栈帧的步骤(代码的执行字节码从我们的方法区中找)
1.创建d和e变量并赋默认值
在这里插入图片描述
2.开始赋值
d赋值为1
e赋值为2
在赋值 f 的时候需要计算,所以将d和e的值压入操作数栈
在这里插入图片描述
然后进行加运算,将两个值弹出操作数栈做加运算后再把运算后的值压入操作数栈
在这里插入图片描述
在这里插入图片描述
然后再将3赋值给f在这里插入图片描述
最后结果
在这里插入图片描述
然后将f的值返回,赋给k变量
count方法出栈,继续执行main方法,加的程序计数器的作用就是为了记录当前进行到哪了,能够正确的显示代码执行的行数,
在这里插入图片描述
main方法执行结束,也出栈,程序结束,
到这里大家应该明白栈是什么作用了,关于栈大家还需了解一个slot单位(局部变量表的单位) 面试可能会问到

本地方法栈

和jvm虚拟机栈唯一的区别就是本地方法栈是执行native(本地方法的),而jvm虚拟机栈是执行java方法的,
同样是线程私有的

程序计数器

就是一个记录当前代码执行的位置,其他的没什么

堆中主要存放的就是对象了,
在这里插入图片描述
jvm将整个堆划分了,分别为年轻代的Eden区,survive区 和 老年代,比例上图已经给出
这只是个分区而已,里面存放的还都是对象,分区是因为方便垃圾回收
关于垃圾回收这里先不详细解释,以后讲gc时讲
我们差的就是了解对象是什么,对象在堆中的具体结构,这部分东西也非常多放在对象构造这一章节讲
这里列举面试中可能提到的几个名词(至少要听说过)
tlab(thread local allocation buffer)
指针碰撞
栈上分配
逃逸分析