一文看懂JVM运行时内存分布

简介: 一文看懂JVM运行时内存分布

 前言

繁忙的一年即将过去,由于若干种原因,下定决心开始写一些基础系列,主要包含Java基础、Android基础、设计模式与算法等,目前还没给这个系列想到一个好听的名字。

虚拟机的实现有很多,比如HotSpot、Android Dalvik 、 ART等,不同虚拟机具体实现方式不同但都符合Java虚拟机规范中的规则。

从1+2来看JVM运行时内存分布

新建一个Test类,定义一个静态方法sum,代码如下所示:

public class Test {
    public static void main(String[] args) {
        System.out.println(sum());
    }
    public static int sum() {
        int a = 1;
        int b = 2;
        return a + b;
    }
}

image.gif

运行程序,打印结果为3。那么运行Test文件的流程是怎样的呢?

JVM内存分布

首先Test.java文件经过编辑器编译生成Test.class文件。当运行Test类时,通过ClassLoader将Test.class加载到JVM内存中,如图1所示。

image.gif

图1 Test.java 执行流程

JVM运行时内存主要分为:程序计数器、虚拟机栈、本地方法栈、堆、方法区五个部分,如图2所示。

image.gif

图2 JVM运行时内存分布

其中方法区和堆是线程间共享的 ,虚拟机栈、本地方法栈和程序计数器是线程私有的,依次来看这些区域各自的作用。

程序计数器

程序计数器用来记录当前线程执行的位置。CPU可以在多个线程中分配执行时间,当某个线程被挂起时,程序计数器用来记录代码已经执行的位置,当线程恢复执行时继续从记录位置开始执行。常见的异常处理、分支操作等都是通过通过程序计数器来完成的。

每个线程内部都有一个程序计数器,随着线程的创建而创建,随着线程的销毁而销毁。计数器记录的是正在执行的虚拟机字节码指令的地址,如果当前执行的是Native方法,计数器值为空。

虚拟机栈

虚拟机栈用来描述Java方法执行的内存模型,我们都知道,JVM是基于栈的解释器执行的,这里的栈指的就是虚拟机栈,更确切的说是虚拟机栈栈帧中的操作数栈。

线程在执行方式时会为每个方法创建一个栈帧,栈帧内部又包含局部变量表、操作数栈、动态链接与返回地址。线程中栈帧分布如图3所示。

image.gif

图3 栈帧结构

局部变量表

局部变量表是变量值的存储空间,调用方法传递的参数、方法内部创建的变量都会保存在局部变量表中。java文件经过编译后局部变量表的大小已经确定,会写在Code属性表中max_locals属性中。

以上面两数相加的代码为例,查看Test文件的字节码代码如下所示:

public static int sum();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: iconst_1
         1: istore_0
         2: iconst_2
         3: istore_1
         4: iload_0
         5: iload_1
         6: iadd
         7: ireturn
      LineNumberTable:
        line 16: 0
        line 17: 2
        line 18: 4

image.gif

从字节码文件中可以看出locals属性的值是2,说明局部变量表的大小为2 分别用来存储变量a和变量b。args_size 表示是参数的个数,这里参数是0,stack表示操作数栈的最大值,首先来看操作数栈是什么。

操作数栈

操作数栈中可以存储任意的Java数据类型。字节码code表中stack=2表示操作数栈的最大深度为2,方法执行的时候会有字节码指令压入或弹出,以上面的字节码操作为例,来看一下操作数栈和局部变量表的变化。

首先开看下各指令值的含义:

iconst:将常量压入操作数栈栈顶,与此类似的还有bipush指令,当 int 取值 -1~5 采用 iconst 指令,取值 -128~127 则使用 bipush 指令。

istore:将操作数栈栈顶元素出栈放入局部变量表的索引位置,istore_n表示将栈顶元素放在局部变量表下标为n的位置。

iload:iload_n表示将局部变量表中下标为n的值压入栈顶

iadd:将操作数栈最上面的两个元素相加,将结果压入栈顶

以1+2的字节码方法为例

0: iconst_1
 1: istore_0
 2: iconst_2
 3: istore_1
 4: iload_0
 5: iload_1
 6: iadd
 7: ireturn

image.gif

刚开始执行sum方式时字局部变量表与操作数栈下图4所示。

image.gif

          图4 局部变量表和操作数栈初始状态

 执行0: iconst_1之后,如图5所示。

image.gif

 图5

执行 1: istore_0之后,如图6 所示。

image.gif

图6

同样的执行

2: iconst_2

3: istore_1

4: iload_0

5: iload_1

6: iadd

依次变化如图7所示。

image.gif

                 图7 第2步到第6步局部变量表与操作数栈变化

最后执行return,将操作数栈中的元素3返回,由此1+2=3的操作边完成了,方法执行完成后局部变量表和操作数栈会被销毁。

我们经常会遇到StackOverflowError的异常,这就是因为我们上面所说的每调用一个方法时都会在虚拟机栈中创建一个栈帧,当遇到异常导致方法无法退出时,栈帧就不会销毁从而导致StackOverflowError的异常。

动态链接

动态链接是为了支持方法调用过程中的动态链接。一个方法若要调用另一个方法,需要将方法的符号引用转化为内存地址的应用,符号引用存储在方法区中。

返回地址

返回地址可以使当前方法恢复上层方法执行状态,便于在方法退出后返回到方法被调用的位置继续执行。

方法退出方式无非就是两种:正常退出和异常退出,正常退出时程序计数器可以作为返回地址,异常退出时返回地址需要通过异常处理器表来确定。

本地方法栈

本地方法栈与虚拟机栈基本相同,主要用来管理native方法,如在Android中使用JNI。这里就不对本地方法栈单独介绍了。

方法区

方法区主要用来存储已被加载的类、静态变量、常量等信息。方法区仅仅是JVM规范中规定的区域,不同的JVM厂商实现方式是不同的。这一点是需要注意的。

堆在JVM管理管理的内存中是最大的一块,堆用来存在对象的实例,也是GC管理的主要区域。

按照存储对象时间不同可以划分为新生代和老年代,其中新生代又分为Eden区和Survivor区,不同的存放区域存放不同生命周期的对象,这样每个区域就可以使用不同的垃圾回收算法,以此来提高垃圾回收率。堆的划分如图8所示。

image.gif

图8 堆区域划分

堆和方法区都是线程间共享的内存区域。

总结

JVM运行时内存主要有程序计数器、虚拟机栈、本地方法栈、堆和方法区,只有堆和方法区是线程间的数据共享区域。

目录
相关文章
|
8月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
768 55
|
3月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
314 5
|
3月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
9月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
756 6
|
10月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
480 29
JVM简介—1.Java内存区域
|
10月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
8月前
|
存储 安全 Java
JVM深入原理(七)(一):运行时数据区
栈的介绍:Java虚拟机栈采用栈的数据结构来管理方法调用中的基本数据,先进后出,每一个方法的调用使用一个栈帧来保存栈的组成:栈:一个线程运行所需要的内存空间,一个栈由多个栈帧组成栈帧:一个方法运行所需要的内存空间活动栈帧:一个线程中只能有一个活动栈帧栈的生命周期:栈随着线程的创建而创建,而回收会在线程销毁时进行栈的执行流程:栈帧压入栈内执行方法执行完毕释放内存若方法间存在调用,那么会压入被调用方法入栈,执行完后释放内存,再执行当前方法,直到执行完毕,释放所有内存。
172 0
|
8月前
|
存储 缓存 安全
JVM深入原理(七)(二):运行时数据区
堆的作用:存放对象的内存空间,它是空间最大的一块内存区域.栈上的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间共享。堆的特点:线程共享:堆中的对象都需要考虑线程安全的问题垃圾回收:堆有垃圾回收机制,不再引用的对象就会被回收方法区的概述:方法区是存放基础信息的位置,线程共享,主要包括:类的元信息:保存了所有类的基本信息运行时常量池:保存了字节码文件中的常量池内容静态常量池:字节码文件通过编号查表的方式找到常量。
112 0
|
10月前
|
存储 Java C++
JVM 运行时数据区
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这 些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区 域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解 析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳 转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成; 为什么要线程计数器?因为线程是不具备记忆功能 Java 虚拟机
|
6月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
2018 0