美文网首页
初识JVM-JVM内存结构

初识JVM-JVM内存结构

作者: BigDreamMaker | 来源:发表于2018-08-26 14:55 被阅读0次

1 前言

什么是JVM?我们来看一下维基百科的答案

A Java virtual machine (JVM) is a virtual machine that enables a computer to run Java programs as well as programs written in other languages and compiled to Java bytecode. The JVM is detailed by a specification that formally describes what is required of a JVM implementation. Having a specification ensures interoperability of Java programs across different implementations so that program authors using the Java Development Kit (JDK) need not worry about idiosyncrasies of the underlying hardware platform.

Java虚拟机(JVM)是一种虚拟机,它使计算机能够运行Java程序以及用其他语言编写的程序(如Groovy,Scala),并编译成Java字节码。JVM由规范描述了JVM实现所需的规范。使用规范确保Java程序在不同实现之间的互操作性,以便使用Java开发工具包(JDK)的程序作者不必担心底层硬件平台的特质。
其中虚拟机指的是

指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。

我们常见的虚拟机有VM Ware,Virtual Box,JVM
而今天我们要介绍的内容是虚拟机中很重要的一个概念,虚拟机的内存结构

2 虚拟机内存结构

JVM内存结构.jpg
其中最重要的概念是 方法区,JAVA堆,JAVA栈,指令计数器(PC)
  1. 指令计数器(PC)
    每个线程都有独立的程序计数器,各线程的互不影响,用于存储下一条执行的虚拟机指令地址(对于Native方法则为空undefined)

  2. JAVA栈(VM Stack)
    我们知道是一种先进后出的数据结构,它有点像机关枪的弹夹先被放进去的子弹最后被打出来。
    JVM会为每一个线程创建一个栈,JAVA中的栈是以栈帧为基本的数据单元,在一个线程栈里面会有很多个栈帧(Frame),每一个栈帧对应一个方法。例如:我们一般在main方法写的程序,叫做主线程中main方法的栈帧。而这个main方法的栈帧存储着,该方法运行时所需要的数据。当main方法调用其他方法例如max()方法。那么max方法所对应的栈帧就会进行压栈操作,成为当前的栈帧。当max()方法执行结束之后,当前的栈帧就会出栈,main方法重新成为当前的栈帧。
    那么一个栈帧主要存储着哪些数据呢?主要包含下面的数据

    • 局部变量表
    • 操作数栈
    • 方法返回地址
    • 动态链接

    2.1 局部变量表:
    局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用(reference)和(returnAddress)类型(它指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成计算的,即在Java程序被编译成Class文件时,就确定了所需分配的最大局部变量表的容量。当进入一个方法时,这个方法需要在栈中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

    2.2 操作数栈:
    操作数栈又常被称为操作栈,操作数栈的最大深度也是在编译的时候就确定了。32位数据类型所占的栈容量为1, 64位数据类型所占的栈容量为2。当一个方法开始执行时,它的操作栈是空的,在方法的执行过程中,会有各种字节码指令(比如:加操作、赋值元算等)向操作栈中写入和提取内容,也就是入栈和出栈操作。Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的。基于栈的指令集最主要的优点是可移植性强,主要的缺点是执行速度相对会慢些;而由于寄存器由硬件直接提供,所以基于寄存器指令集最主要的优点是执行速度快,主要的缺点是可移植性差。

    2.3 动态链接:
    每个栈帧都包含一个指向运行时常量池(在方法区中,后面介绍)中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如 final、static 域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。

    2.4 方法返回地址:
    当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。方法退出的过程实际上等同于把当前栈帧出站,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。

  1. JAVA堆(Heap)
    所有线程共享的内存区域,用于存放对象实例
  2. 方法区(Method Area)
    线程共享,用于存放已加载的类、常量、静态变量、JIT编译后的代码等数据。
    对于HotSpot虚拟机用户而言,经常将方法区称为永生代(Permanent Generation),是因为HotSpot虚拟机用永生代实现方法区,用GC管理方法区。
  3. 综合案例
    综合例子.jpg
public class Sample {
    private String name;
    public Sample(String name) {
        this.name = name;
    }
    public void printName() {
        System.out.println(name);
    }
}
public class appMain {
    public static void main(String[] args) {
        Sample test1 = new Sample("测试1");
        Sample test2 = new Sample("测试2");   
        test1.printName();
        test2.printName();
    }
}

程序的执行流程如下。

  1. appMain类信息(即appMain.class对象)通过类加载器加载进方法区
  2. test1,test2 是自定义类Sample类的两个引用,放置在主线程main方法对应的栈帧中。
  3. Sample类的两个实例放置在堆区中。
  4. 分别调用在test1,test2,关于Sample存储在方法区中的printName()方法。
  5. 返回main方法,程序结束。

相关文章

网友评论

      本文标题:初识JVM-JVM内存结构

      本文链接:https://www.haomeiwen.com/subject/dqdoiftx.html