JVM: 结合一个例子理解JVM内存模型原理

  • JVM内存结构图如下所示,主要有三大块:堆内存、方法区和栈

JVM会通过类装载子系统将以class结尾的字节码文件装载到JVM的运行时数据区,然后cpu通过执行引擎不断与运行时数据区进行交互来执行程序。图中紫色模块是线程私有的;黄色部分是线程共享的。
JVM: 结合一个例子理解JVM内存模型原理_第1张图片

  • 本次使用的IDE是IDEA, 因为要结合例子理解内存模型,所以首先创建一个Math类如下:
package cn.ly.jvm;

public class Math {
    public int math() {
        int a=2;
        int b=3;
        int c=(b-a)*6;
        return c;
    }
    public static void main(String[] args) {
        Math math=new Math();
        math.math();
    }
}

Math.java经过编译后在target目录下会生成对应的字节码文件Math.class。如下图所示,右击Math.class文件,打开terminal,通过执行 javac -c Math.class > math.txt 命令对Math.class进行反汇编并将结果保存到同一目录下的Math.txt中。
JVM: 结合一个例子理解JVM内存模型原理_第2张图片
Math.txt的内容如下,

Compiled from "Math.java"
public class cn.ly.jvm.Math {
  public cn.ly.jvm.Math();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

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

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class cn/ly/jvm/Math
       3: dup
       4: invokespecial #3                  // Method "":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method math:()I
      12: pop
      13: return
}

Math.java中的程序从main开始执行,JVM会为main线程分配一个栈内存空间。此处要提到栈帧这个重要的概念。栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,他是虚拟机运行时数据区的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法的返回地址等信息。每一个方法从调用开始直至执行完成的过程,都对应的一个栈帧在虚拟机栈里入栈和出栈的过程。简言之,即每个方法对应一个栈帧,不同方法的操作数栈、动态链接和方法的返回地址存储在不同方法的栈帧中。

  • 下面用图来说明执行引擎在执行math方法的字节码指令时JVM内存中的情况。

字节码指令的含义可以直接参考字节码指令手册:链接:https://pan.baidu.com/s/1jBJwJlkL0LZ_bpki9oC-LQ
提取码:sdy7

执行了以下字节码指令后,局部变量1即a =2,详见下图:

   Code:
      0: iconst_2  // 将int类型的常量2压入操作数栈
      1: istore_1  //将int类型的值2从操作数栈出栈,存入局部变量1(此处即a)中

JVM: 结合一个例子理解JVM内存模型原理_第3张图片

执行了以下字节码指令后,局部变量2即 b=3, 详见下图:

    Code:
       2: iconst_3  //将int类型的常量3压入操作数栈
       3: istore_2  //将int类型的值3从操作数栈出栈,存入局部变量2中

JVM: 结合一个例子理解JVM内存模型原理_第4张图片

执行了以下字节码指令后,结果如下图:

    Code:
       4: iload_2  //将局部变量2的值加载到操作数栈中
       5: iload_1  //将局部变量1的值加载到操作数栈中

JVM: 结合一个例子理解JVM内存模型原理_第5张图片
执行了以下字节码指令后,结果如下图:

   Code:
       6: isub     //执行int类型的减法,此时操作数3与2先后出栈

JVM: 结合一个例子理解JVM内存模型原理_第6张图片
执行了以下字节码指令后,结果如下图:

	Code:
	    7: bipush        6  //执行减法的结果1入操作数栈,同时常量6入操作数栈。

JVM: 结合一个例子理解JVM内存模型原理_第7张图片
执行了以下字节码指令后,结果如下图:

    Code:
       9: imul  //执行int类型的乘法,此时操作数6与1先后出栈,执行乘法运算,并将结果6入操作数栈。

JVM: 结合一个例子理解JVM内存模型原理_第8张图片
执行了以下字节码指令后,结果如下图:

	Code:
      10: istore_3  //将常量6从操作数栈出栈,赋值给int型变量3
      11: iload_3   //将局部变量3的值加载到操作数栈中
      12: ireturn   //操作数栈中的栈顶元素6出栈,根据方法返回地址的值,返回到main方法中对应的位置

JVM: 结合一个例子理解JVM内存模型原理_第9张图片
至此math方法执行结束。

当在main方法中new一个Math对象时,JVM根据方法区中类元信息的数据结构创建一个Math类的对象实例并保存到堆内存中。(在math对象的对象头中有指向方法区中Math.class类元信息的指针)
JVM: 结合一个例子理解JVM内存模型原理_第10张图片
方法区:HotSpot虚拟机的设计团队使用永久代(Permanent Generation)实现方法区,因此,很多人将方法区称为永久代(PermGen)。java8开始,永久代被元空间(Metaspace)取代。由于加载到方法区的字节码大小无法确定,为了防止内存溢出,元空间并不在虚拟机内存中,而是使用本地内存。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

本地方法栈:它与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

程序计数器:程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。

Java堆是Java虚拟机管理的内存中最大的一块,此内存区域是为了存放对象实例而存在,所以对象实例以及数组都在堆中分配内存。它也是垃圾回收(GC)的主要区域,因此也被称为GC堆。

由于现有的收集器基本采用分代收集算法,所以Java堆可细分为:新生代和老年代,内存比例一般为2:1,新生代又可分为Eden区,From Survivor区,To Survivor区,内存比例一般为8:1:1。如下图所示:
JVM: 结合一个例子理解JVM内存模型原理_第11张图片

你可能感兴趣的:(JVM: 结合一个例子理解JVM内存模型原理)