图解JVM内存模型及JAVA程序运行原理

本文涉及的产品
模型训练 PAI-DLC,100CU*H 3个月
模型在线服务 PAI-EAS,A10/V100等 500元 1个月
交互式建模 PAI-DSW,每月250计算时 3个月
简介: 本文主要介绍了JVM内存模型及JAVA程序运行原理。

来源|阿里开发者公众号

作者|伍玉莹(姬无)

关注阿里开发者】公众号查看更多精品技术文章或精品电子书


一、JAVA语言的特点

在进入正题之前,先问一个老生常谈的问题,相较于C,JAVA语言的优势是什么?相信学过JAVA的人都知道,无论是大学时的第一堂课还是JAVA相关书籍的第一章也都会讲到:一次编写、到处运行;真正意义上的实现了跨平台。那再问一个问题,为什么Java可以跨平台?大多数人都知道Java可以跨平台得益于 JVM(java虚拟机)。在这之前,我了解到的java跨平台得益于不同版本的JVM,那么它的底层原理是什么呢?“一次编译,到处运行” 是Java的跨平台特性。像 C 、C++ 这样的编程语言没有它。通过下面的介绍,相信你会有一个近一步的了解。Java是一种可以跨平台的编程语言。首先,我们需要知道什么是平台。我们把CPU处理器与操作系统的整体叫平台。CPU相当于计算机的大脑,指令集是CPU中用来计算和控制计算机系统的一套指令的集合。指令集分为精简指令集(RISC)和复杂指令集(CISC)。每个CPU都有自己的特定指令集。要开发一个程序,我们必须首先知道程序运行在什么CPU上,也就是说,我们必须知道CPU使用的指令集。操作系统是用户与计算机之间的接口软件。不同的操作系统支持不同的CPU。严格来说,不同的操作系统支持不同的CPU指令集。但问题是,原来的Mac操作系统只支持PowerPC,不能安装在英特尔上。我们该怎么办?因此,苹果必须重写其Mac操作系统来支持这一变化。最后,我们应该知道不同的操作系统支持不同的CPU指令集。现在windows、Linux、MAC和Solaris都支持Intel和AMD CPU指令集。如果你想开发一个程序,首先应该确定:

  • CPU类型,即指令集类型;
  • 操作系统;我们称之为软硬件平台的结合。也可以说“平台=CPU+OS”。而且由于主流操作系统支持主流CPU,有时操作系统也被称为平台。

二、如何实现跨平台

通常,我们编写的Java源代码在编译后会生成一个Class文件,称为字节码文件。Java虚拟机负责将字节码文件翻译成特定平台下的机器代码,然后运行。简言之,java的跨平台就是因为不同版本的 JVM。换句话说,只要在不同的平台上安装相应的JVM,就可以运行字节码文件(.class)并运行我们编写的Java程序。在这个过程中,我们编写的Java程序没有做任何改动,只是通过JVM的“中间层”,就可以在不同的平台上运行,真正实现了“一次编译,到处运行”的目的。JVM是跨平台的桥梁和中间件,是实现跨平台的关键。首先将Java代码编译成字节码文件,然后通过JVM将其翻译成机器语言,从而达到运行Java程序的目的。因此,运行Java程序必须有JVM的支持,因为编译的结果不是机器代码,必须在执行前由JVM再次翻译。即使您将Java程序打包成可执行文件(例如。Exe),仍然需要JVM的支持。注意:编译的结果不是生成机器代码,而是生成字节码。字节码不能直接运行,必须由JVM转换成机器码。编译生成的字节码在不同的平台上是相同的,但是JVM翻译的机器码是不同的。

三、JVM简介

JVM------Java Virtual Machine.JVM是Java平台的基础,与实际机器一样,他有自己的指令集(类似CPU通过指令操作程序运行),并在运行时操作不同的内存区域(JVM内存体系)。Java虚拟机位于操作系统之上(如下图所示),将通过JAVAC命令编译后的字节码加载到其内存区域,通过解释器将字节码翻译成CPU能识别的机器码行。每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里。

image.png

JVM是运行在操作系统之上的,它与硬件没有直接交互。

四、JVM的内存结构

JAVA源代码文件通过编译后变成虚拟机可以识别的字节码,JAVA程序在执行时,会通过类加载器把字节码加载到虚拟机的内存中(虚拟机的内存是一个逻辑概念,相当于是对主内存的一个抽象,实际上真实的数据还是存放在主存中),详见下图。

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。每个区域都有各自的作用。分析 JVM 内存结构,主要就是分析JVM 运行时数据存储区域。JVM 的运行时数据区主要包括:堆、栈、方法区、程序计数器等。而 JVM 的优化问题主要在线程共享的数据区中:堆、方法区。

4.1、方法区

又称非堆(non-heap),方法区用于存储已被虚拟机加载的类信息,常量、静态变量,即时编译后的代码等数据。方法区中最著名的就是CLASS对象,CLASS对象中存放了类的元数据信息,包括:类的名称、类的加载器、类的方法、类的注解等。当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。注意,我们定义的一个类,无论创建多少个实例对象,在JVM中都只有一个Class对象与其对应,即:在内存中每个类有且只有一个相对应的Class对象,如图:

实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件)。注:使用new创建类的新实例对象也会被当作对类的静态成员的引用(构造函数也是类的静态方法)由此看来Java程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载,所以在使用该类时,类加载器首先会检查这个类的Class对象是否已被加载(类的实例对象创建时依据Class对象中类型信息完成的),如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以根据这个类的Class对象来创建这个类的所有实例对象。

4.2、堆

所有创建出来的实例对象还有数组都是存放在堆内存中,堆是Java虚拟机所管理的内存中最大的一块存储区域,堆内存被所有线程共享。垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间,堆上又分为了新生代和老年代,针对不同的分代又会有对象的垃圾回收器和相应的回收算法(GC章节中会详细介绍)。

4.3、栈

JVM 中的栈包括 Java 虚拟机栈和本地方法栈,两者的区别就是,Java 虚拟机栈为 JVM 执行 Java 方法服务,本地方法栈则为 JVM 使用到的 Native 方法服务。两者作用是极其相似的,本文主要介绍 Java 虚拟机栈,以下简称栈。栈属于线程私有的数据区域,与线程同时创建,总数与线程关联,代表Java方法执行的内存模型。每个方法执行时都会创建一个栈帧来存储方法的的局部变量表、操作数栈、动态链接方法、方法返回值、返回地址等信息。每个方法从调用值结束就对于一个栈桢在虚拟机栈中的入栈和出栈过程,中的局部变量表可以存放基本类型,也可以存放指向对象的引用,当在某个方法中new Object()时,会在当前方法栈帧中的局部变量表存放一个指向堆内存实例对象的引用,详见下图。

4.4、程序计数器

是一块较小的内存空间,用来存储虚拟机下一条执行的字节码指令地址,和CPU中的程序计数器是一样的概念。

五 、JAVA程序在JVM内是如何执行的

上文已介绍了JVM的内存结构,接下来再看一下这个程序在JVM内部是怎么运行的:

1.JAVA程序的执行过程简单来说包括:

2.JAVA源代码编译成字节码;

3.字节码校验并把JAVA程序通过类加载器加载到JVM内存中;

4.在加载到内存后针对每个类创建Class对象并放到方法区;

5.字节码指令和数据初始化到内存中;

6.找到main方法,并创建栈帧;

7.初始化程序计数器内部的值为main方法的内存地址;

8.程序计数器不断递增,逐条执行JAVA字节码指令,把指令执行过程的数据存放到操作数栈中(入栈),执行完成后从操作数栈取出后放到局部变量表中,遇到创建对象,则在堆内存中分配一段连续的空间存储对象,栈内存中的局部变量表存放指向堆内存的引用;遇到方法调用则再创建一个,压到当前的上面。下面以一段实际的代码举例,来看一下,程序在JVM内部的执行过程。



我们先通过JAVAP命令,展示上述代码对应的字节码,下图是JVM把类加载到内存以后在方法区的常量池中初始化好的Class对象和各种方法引用,这里面需要重点关注一下前面的#1,#2,#5这些符号,这些数字保存的是和Class对象以及方法的引用关系,后面的字节码中会用到。



随后执行引擎中的解释器会率先启动,对ClassFile字节码采用逐行解释的方式加载机器码,并配合运行时数据区的程序计数器与操作数栈来支持。下图是main方法的字节码指令,我们结合JVM内存情况对代码做逐一分析。

上图中stack=3,local=2:stack=3代表栈的深度为3,local=2代表局部变量表中的变量数量。


程序样例执行详解

下图是main方法中的字节码执行到detail.Sum方法前的JVM内存结构。

具体执行流程如下:    首先会在JAVA栈中压入main方法的,然后程序计数器中的值更新成字节码new所在的内存地址,样例中为了方便起见就直接以0表示,程序计数器逐条解析字节码,其中new(new后面的#5中有讲到,对应的是JvmDetailClass的Class对象),dup,invokespecial三个字节码指令分别代表创建对象、赋值引用、调用构造方法,astore_1代表是把操作数(引用)放入操作数栈,aload_1代表是把操作数(引用)出栈,并放到局部变量表中。Iconst_3,iconst_5分别代表把操作数3,5入栈放到操作数栈中。接下来我们再看一下方法调用时JVM的内存结构是怎么做的,上面的代码中涉及到2块代码调用,一个是detail.Sum,一个是detail.getSum,这里我们detail.getSum是个带有返回值的方法,比较典型,我们直接以detail.getSum的调用为样例,看一下JVM内部是怎么执行的。当解释器执行到方法调用时,会修改程序计数器中的值为调用的方法内部第一行指令,同时在栈中压入getSum方法的,压入后会在局部变量表中初始化一个当前方法所属的对象的引用this,如果调用方法涉及到传参的情况下,则会在局部变量表中存入传递的参数。当getSum方法执行完成后,会做两步动作:程序计数器又会修改为main方法调用getSum处的下一行指令的地址。方法返回值写入main方法中的操作数栈中。



相关文章
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
291 1
|
14天前
|
人工智能 物联网 C语言
SVDQuant:MIT 推出的扩散模型后训练的量化技术,能够将模型的权重和激活值量化至4位,减少内存占用并加速推理过程
SVDQuant是由MIT研究团队推出的扩散模型后训练量化技术,通过将模型的权重和激活值量化至4位,显著减少了内存占用并加速了推理过程。该技术引入了高精度的低秩分支来吸收量化过程中的异常值,支持多种架构,并能无缝集成低秩适配器(LoRAs),为资源受限设备上的大型扩散模型部署提供了有效的解决方案。
39 5
SVDQuant:MIT 推出的扩散模型后训练的量化技术,能够将模型的权重和激活值量化至4位,减少内存占用并加速推理过程
|
3天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
3天前
|
Rust 安全 Java
JVM原理与实现——Synchronized关键字
在多线程Java程序中,`Synchronized`关键字用于确保线程安全。本文深入探讨其工作原理,通过分析字节码`monitorenter`和`monitorexit`,解释JVM如何实现同步机制。文章展示了`Synchronized`方法的编译结果,并详细解析了轻量锁和重度锁的实现过程,包括Mark Word的状态变化及CAS操作的应用。最后简要介绍了`ObjectMonitor::enter()`函数在获取重度锁时的作用。
JVM原理与实现——Synchronized关键字
|
2月前
|
NoSQL 测试技术
内存程序崩溃
【10月更文挑战第13天】
147 62
|
23天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
28天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
62 1
|
29天前
|
Oracle 安全 Java
深入理解Java生态:JDK与JVM的区分与协作
Java作为一种广泛使用的编程语言,其生态中有两个核心组件:JDK(Java Development Kit)和JVM(Java Virtual Machine)。本文将深入探讨这两个组件的区别、联系以及它们在Java开发和运行中的作用。
80 1
|
1月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
1月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
22 3

热门文章

最新文章