美文网首页
JVM 内存模型

JVM 内存模型

作者: 必成_d2f5 | 来源:发表于2020-02-04 15:31 被阅读0次

前言

JAVA的核心特点是其著名的Write once Run anywhere:“编写一次,随处运行”。为了实现它,Sun Microsystems创建了Java虚拟机,这是对底层OS的抽象,它解释执行了编译后的Java代码。JVM是JRE(Java运行环境)的核心组件。

本文将重点介绍JVM规范中描述的Runtime Data Areas(运行时数据区)。这些区域旨在存储程序或JVM本身使用的数据。首先,我将概述JVM,然后是什么字节码,最后是不同的数据区域的特点。

 概况

JVM是底层OS的抽象。它确保无论JVM在什么硬件或操作系统上运行,相同的代码都将以相同的动作运行。例如:

1. 无论JVM是在16位/ 32位/ 64位OS上运行的,原始类型int的大小始终是从-2 ^ 31到2 ^ 31-1的32位有符号整数。

2. 无论底层的OS /硬件是big-endian还是little-endian,每个JVM都以大端顺序(高字节在前)存储和使用内存中的数据。

注意:有时,一个JVM实现的行为与另一个不同,但通常是相同的。

JVM层次结构图:

JVM 解释(执行)由编译类的源代码产生的字节码。尽管术语JVM代表“ Java虚拟机”,但它可以运行其他语言,例如scala或groovy,只要它们可以编译成Java字节码即可。

为了减少磁盘I / O,字节码由运行时数据区之一中的类加载器加载到JVM 中。此代码将保留在内存中,直到JVM停止运行或销毁(加载了它的)类加载器为止。然后,加载的代码由执行引擎解释并执行。执行引擎需要存储运行时数据,例如指向正在执行的代码的指针。它还需要存储在开发人员代码中处理的数据。执行引擎还负责处理底层操作系统相关的交互。

注意:许多JVM实现的执行引擎不是总是解释字节码,为了提升性能,有时会将字节码编译为本地代码(如果经常使用的话)。它称为即时(JIT)编译,可大大加快JVM的速度。

 基于堆栈的架构

JVM使用基于堆栈的体系结构。尽管它对于开发人员是不可见的,但它对生成的字节码和JVM体系结构具有巨大的影响,这就是为什么我将简要解释该概念。

JVM通过执行Java字节码中描述的基本操作来执行开发人员的代码(我们将在下一章中看到)。操作数是指令对其进行操作的值。根据JVM规范,这些操作要求参数通过称为操作数栈的栈传递。

例如,让我们对2个整数进行基本加法。该操作被称为IADD。如果要在字节码中执行3+4:

他首先在操作数堆栈中压入3和4。

然后调用iadd指令。

iadd将弹出操作数堆栈的最后2个值。

将int结果7(3 + 4)压入操作数堆栈,以供其他操作使用。

这种执行方式称为基于堆栈的体系结构。还有其他处理基本操作的方法,例如,基于寄存器的体系结构:将操作数存储在较小的寄存器中,而不是堆栈中。台式机/服务器(x86)处理器(CPU)和android虚拟机Dalvik使用的就是基于寄存器的体系结构。

字节码

Java字节码是将java源代码编译为为一组基本操作的.class文件。每个操作由代表执行指令的一个字节(称为操作码操作代码)以及用于传递参数的零个或多个字节组成(但大多数操作使用操作数堆栈来传递参数)。类似CPU指令的操作码和操作数。

以下是字节码的分类列表:

Constant(常量):用于将常量池中的值(我们将在后面介绍)或将已知值中的值推入操作数堆栈中。从值0x00到0x14

Load(加载):用于将局部变量的值加载到操作数堆栈中。从值0x15到0x35

Store(存储):用于从操作数堆栈存储到局部变量。从值0x36到0x56

Stack(堆栈):用于处理操作数堆栈。从值0x57到0x5f

Math:对操作数堆栈中的值进行基本数学运算。从值0x60到0x84

Convert(转换):用于从一种类型转换为另一种类型。从值0x85到0x93

Compare(比较):用于两个值之间的基本比较。从值0x94到0xa6

Control(控制):诸如goto,return等基本操作,允许进行更高级的操作,例如循环或返回值的函数。从值0xa7到0xb1

Refence(引用):用于分配对象或数组,获取或检查对象,方法或静态方法的引用。也用于调用(静态)方法。从值0xb2到0xc3

Extension(扩展):之后添加的其他类别的操作。从值0xc4到0xc9

Reserve(保留):供每个Java虚拟机实现内部使用。3个值:0xca,0xfe和0xff。

Java 8使用了204个操作码,都非常简单,例如:

操作数ifeq(0x99)检查2个值是否相等

iadd操作数(0x60)将2个值相加

操作数i2l(0x85)将整数转换为long

操作数arraylength(0xbe)给出数组的大小

操作数pop(0x57)从操作数堆栈中弹出第一个值

要创建字节码,需要一个编译器,JDK中包含的标准java编译器是javac

让我们看一下简单的添加:

public class Test {

  public static void main(String[] args) {

    int a =1;

    int b = 15;

    int result = add(a,b);

  }

  public static int add(int a, int b){

    int result = a + b;

    return result;

  }

}

“ javac Test.java”命令将生成Test.class文件,这个文件就是编译后的字节码。由于Java字节码是二进制代码,因此可读性不好。Oracle在其JDK 提供了一个工具javap,该工具可以将二进制字节码转换可读性更好的版本。读者可自行尝试。

以下是存储在.class文件中的字节码的各字段的简要说明:

ClassFile {

  u4 magic;

  u2 minor_version;

  u2 major_version;

  u2 constant_pool_count;

  cp_info constant_pool[constant_pool_count-1];

  u2 access_flags;

  u2 this_class;

  u2 super_class;

  u2 interfaces_count;

  u2 interfaces[interfaces_count];

  u2 fields_count;

  field_info fields[fields_count];

  u2 methods_count;

  method_info methods[methods_count];

  u2 attributes_count;

  attribute_info attributes[attributes_count];

}

运行时数据区

运行时数据区是存储程序运行时数据的内存区域。

下图是JVM中不同运行时数据区域的概述。每个线程的都有独立的栈和pc register。

堆是在所有Java虚拟机线程之间共享的内存区域。它是在虚拟机启动时创建的。所有类实例数组都在堆中分配(使用new运算符)。

MyClass myVariable = newMyClass();

MyClass[] myArrayClass = newMyClass[1024];

当不再使用分配的实例时,必须由垃圾收集器 来管理该区域。清理内存的策略取决于JVM实现(例如,Oracle Hotspot提供了多种算法)。

堆可以动态扩展或收缩,并且可以具有固定的最小和最大大小。例如,在Oracle Hotspot中,用户可以通过以下方式用Xms和Xmx参数指定堆的最小大小:“ java -Xms = 512m -Xmx = 1024m…”。

注意:堆不能超过最大大小。如果超出此限制,JVM将抛出OutOfMemoryError。

方法区

方法区域是所有Java虚拟机线程之间共享的内存。它是在虚拟机启动时创建的,并由类加载器从字节码加载。只要加载它们的类加载器处于活动状态,方法区域中的数据就会保留在内存中。

方法区域存储:

类信息(字段/方法的数量,超类名称,接口名称,版本等)

方法和构造函数的字节码。

每个类加载的运行时常量池。

规范不强制在堆中实现方法区域。例如,在JAVA7之前,Oracle HotSpot使用一个名为PermGen的区域来存储“方法区域”。该PermGen与Java堆(以及由JVM像堆一样由JVM管理的内存)是连续的,并且被限制为默认空间64Mo(由参数-XX:MaxPermSize修改)。从Java 8开始,HotSpot现在将“方法区域”存储在称为Metaspace的本机native内存空间中,最大可用空间是总可用系统内存。

注意:方法区域不能超过最大大小。如果超出此限制,JVM将抛出OutOfMemoryError。

运行时常量池

该池是“方法区”的子部分。由于它是元数据的重要组成部分,因此Oracle规范描述了“方法区域”之外的运行时常量池。对于每个加载的类/接口,此常量池都会增加。该池就像常规编程语言的符号表。换句话说,当引用一个类,方法或字段时,JVM使用运行时常量池在内存中搜索实际地址。它还包含常量值,例如字符串文字或常量图元。

String myString1 = “This is a string litteral”;

staticfinalintMY_CONSTANT=2;

pc寄存器(每个线程)

每个线程都有自己的pc(程序计数器)寄存器,与该线程同时创建。在任何时候,每个Java虚拟机线程都在执行单个方法的代码,即该线程的当前方法。pc寄存器包含当前正在执行的Java虚拟机指令的地址(在方法区域中)。

注意:如果线程当前正在执行的方法是本机的,则Java虚拟机的pc寄存器的值是未定义的.Java虚拟机的pc寄存器足够宽,可以在特定平台上保存returnAddress或本机指针。

Java虚拟机栈(每个线程)

堆栈区域存储运行时的栈帧,因此在讨论堆栈之前,将先介绍栈帧

栈帧(frame)

栈帧frame是一种用于实现函数调用的数据结构,其中包含多个数据,这些数据表示当前方法(正在调用的方法)中线程的状态:

操作数堆栈:在基于堆栈的体系结构一章中,我已经介绍了操作数堆栈。字节码指令使用此堆栈来处理参数。此堆栈还用于在(java)方法调用中传递参数,并在调用方法的堆栈顶部获取被调用方法的结果。

局部变量数组:此数组包含当前方法范围内的所有局部变量。该数组可以保存基本类型,引用或returnAddress的值。该数组的大小是在编译时计算的。Java虚拟机使用局部变量在方法调用时传递参数,被调用方法的数组是从调用方法的操作数堆栈中创建的。

运行时常量池引用:引用正在执行的当前方法当前类的常量池。JVM使用它将符号方法/变量引用(例如myInstance.method())转换为实内存引用。

每个Java虚拟机线程都有一个私有Java虚拟机堆栈,与该线程同时创建。Java虚拟机堆栈存储栈帧。每次调用方法时,都会创建一个新框架并将其放入堆栈中。框架的方法调用完成时,无论该完成是正常的还是突然的(它引发未捕获的异常),它都会被销毁。

给定线程中的任何一点都只有一个框架(用于执行方法的框架)处于活动状态。该帧称为active frame(当前帧),其方法称为当前方法。定义当前方法的当前类。局部变量和操作数堆栈上的操作通常参考当前帧。

让我们看下面的示例,它是一个简单的加法

public int add(inta, intb){

  returna + b;

}

public void functionA(){

// some code without function call

  intresult = add(2,3); //call to function B

// some code without function call

}

当functionA()在其上运行时,这是它在JVM内部的工作方式:

在functionA()内部,Frame A是堆栈框架的顶部,并且是当前Frame。在内部调用add()的开始处,将新Frame(Frame B)放入堆栈中。帧B成为当前帧。通过弹出帧A的操作数堆栈来填充帧B的局部变量数组。add()完成后,帧B被销毁,帧A再次成为当前帧。add()的结果放在Frame A的操作数堆栈上,以便functionA()可以通过弹出其操作数堆栈来使用它。

注意:栈可以很方便高效的动态扩展和收缩。每个线程的栈的最大值可以设定。这限制了递归调用的数量。如果超出此限制,则JVM抛出 StackOverflowError

使用Oracle HotSpot,可以使用参数-Xss指定此限制。

native方法堆栈

这是用非Java语言编写并通过JNI(Java本机接口)调用的本机代码的堆栈。由于它是一个“native”堆栈,因此该堆栈的行为完全取决于底层操作系统。

相关文章

  • JVM内存模型(jvm 入门篇)

    概述 jvm 入门篇,想要学习jvm,必须先得了解JVM内存模型,JVM内存模型,JVM内存模型,JVM内存模型,...

  • JVM

    栈容量由-Xss指定深入理解JVM—JVM内存模型 JVM内存模型和JVM参数的关系

  • [Java多线程编程之八] Java内存模型

    一、Java内存模型 == JVM内存模型?   很多人都会认为Java内存模型就是JVM内存模型,但实际上是错的...

  • JVM问题及解答

    常见JVM问题 JVM内存模型,GC机制和原理。注意JVM内存模型与Java内存模型(JMM)不是同一个东西。JV...

  • JVM内存结构和Java内存模型

    最近看到两个比较容易混淆的概念:JVM内存结构和Java内存模型 JVM内存结构JVM内存结构或者说内存模型指的是...

  • 高效并发

    从JVM的角度看一下Java与线程,内存模型,线程安全以及JVM对于锁的优化 硬件内存模型与JVM内存模型 硬件的...

  • jvm

    1.5.1JVM的内存模型 首先我们来了解一下JVM的内存模型的怎么样的: 基于jdk1.8画的JVM的内存模型-...

  • JVM基础知识点

    1. 内存模型以及分区,需要详细到每个区放什么(共分为5个)。 JVM内存模型及分区jvm内存模型和内存分配 程序...

  • 面试系列之JVM

    1.jvm内存模型 jvm内存模型主要有运行时期模型和非运行时期两部分组成,通常说的jvm内存模型是指运行时期内存...

  • jvm内存模型

    Java虚拟机内存模型 计划发布3篇博客, 这是第一篇:jvm内存模型 jvm内存模型 对象创建和内存分配 OOM...

网友评论

      本文标题:JVM 内存模型

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