《深入理解Java虚拟机》学习笔记系列-运行时数据区(概念扫盲)(一)

1 导读

经常会看到JVM内存模型,其实说的就是JVM运营时数据区的一个结构,这篇文章主要记录了对不同JDK版本运营时数据区的学习做一个总结。

2 运行时数据区

2.1 什么是运行时数据区

Java虚拟机在执行程序的过程中,为了方便对程序进行内存管理,会将他管理的内存区域划分为几个不同作用的区域。JVM运行时数据区包含以下图中几个区域,其中堆、方法区是不同线程共享的,而程序计数器、本地方法栈、虚拟机栈是线程私有的。

运行时数据.png

2.2 运行时数据区介绍

2.2.1 程序计数器

简单来讲,程序计数器是程序执行当前字节码的位置信息,每一个线程都有私有的程序计数器,这样就可以保证每个线程可以独立运行自己的代码,减少与其他线程的交互,提高代码执行效率。

在《Java虚拟机规范》中,如果当前执行的不是本地方法,那么记录的是当前指令的地址;如果执行的是本地方法,那么这个值是未指定的(Undefind),这块区域很大,足以放下指令指针和本地指针。

2.2.2 虚拟机栈

每一个Java线程都有一个私有的虚拟机栈,与线程生命周期同步。当每个方法被执行的时候,Java虚拟机会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用到调用结束,就对应一个栈帧从入栈到出栈。

public class JVMTest {
​
 public static void main(String[] args) {
​
 int i = 0;
 String str = "abs";
 JVMTest jvmTest = new JVMTest();
 String m = jvmTest.m(str);
 System.out.println(m);
 }
​
 private String m(String m) {
​
 return m;
 }
​
 private static String m2(String m2) {
 return m2;
 }
}
  • 局部变量表

    局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。

    下面图片是main方法的局部变量表,第一个局部变量是形参0-args,后面依次是 1-i、2-str、3-jvmTest、4-m变量

局部变量表.png
  • 操作数栈

    下面是main方法的字节码指令

     0 iconst_0 #将int类型常量值0压入栈
     1 istore_1 #将int类型值存入局部变量1
     2 ldc #2  #把常量池中的项压入栈
     4 astore_2 #将引用类型或returnAddress类型值存入局部变量2
     5 new #3  #创建一个JVMTest新对象
     8 dup #复制栈顶部一个字长内容
     9 invokespecial #4 > #调用初始化方法(之后会在类加载中详细讲解init方法)
    12 astore_3 #将引用类型或returnAddress类型值存入局部变量3
    13 aload_3 #从局部变量中装载引用类型值
    14 aload_2 #从局部变量中装载引用类型值
    15 invokespecial #5  #调用m方法
    18 astore 4 #将引用类型或returnAddress类型值存入局部变量4
    20 getstatic #6  #从类中获取静态字段
    23 aload 4 #从局部变量中装在引用类型值
    25 invokevirtual #7  #调用println方法
    28 return #从方法中返回,返回值为void

下面是m方法的字节码指令

    0 aload_1 #从局部变量1中装载引用类型值
    1 areturn #从方法中返回引用类型的数据

那么问题来了,明明方法m中形参是第一个局部变量,为什么是aload_1而不是aload_0?

这是因为如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存在index为0 的Slot处,非静态方法,都会创建this的一个参数,index为0,其余的参数是按照顺序排放的,static 方法被不可以使用this是因为static方法中没有放this的index。

再来看一下m2方法字节码指令

    0 aload_0
    1 areturn
  • 方法出口

    通俗的讲,每一个方法在执行完或者抛出异常都需要回到上一个方法调用的地方,这就是方法出口。

  • 动态链接

    通俗的讲,我们在方法调用时并不是直接将方法的信息保存在同一个栈帧中,而是指向该方法在常量池中的地址引用。

2.2.3 本地方法栈

​ 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机 栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。

2.2.4 方法区

​ 它用于存储已被虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。我们平时操作new对象时所需要的类信息就存储在方法区。(之后会在类加载中详细讲解)

​ 在JDK1.8以前,方法区存放的位置可以理解成永久代,但是到了JDK1.8,用元数据区(metaspace)取代了永久代。永久代是需要分配内存大小的,而元数据区在不指定大小的情况下,受限于物理内存

2.2.5 堆

​ 对于Java程序来说,堆是JVM管理内存最大的一部分,此内存部分是用于存放对象实例。根据对象的年龄,可以分为年轻代老年代。在年轻代中又可以分为Eden区,Survive区,之后要讲到的GC也主要是针对堆进行展开。

3 总结

想要对JVM有更深入的理解,必须先搞清楚每个数据区的作用,以及代码是如何在各个数据区串联起来的。

你可能感兴趣的:(《深入理解Java虚拟机》学习笔记系列-运行时数据区(概念扫盲)(一))