一文看懂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运行时内存主要有程序计数器、虚拟机栈、本地方法栈、堆和方法区,只有堆和方法区是线程间的数据共享区域。

目录
相关文章
|
26天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
24天前
|
Java Linux Windows
JVM内存
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制。
19 1
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
73 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
2月前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
51 10
|
2月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
55 2
|
2月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
56 3
|
2月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
4月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
399 0
|
2月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
80 1