关于jvm:图解-JVM-内存模型及-JAVA-程序运行原理

43次阅读

共计 4488 个字符,预计需要花费 12 分钟才能阅读完成。

一、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 指令集。如果你想开发一个程序,首先应该确定:

  1. CPU 类型,即指令集类型;
  2. 操作系统;咱们称之为软硬件平台的联合。也能够说“平台 =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 虚拟机标准中都有具体定义,如怎么取操作数,怎么解决操作数,处理结果放在哪里。

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 办法栈帧中的操作数栈中。

作者|伍玉莹(姬无)

点击立刻收费试用云产品 开启云上实际之旅!

原文链接

本文为阿里云原创内容,未经容许不得转载。

正文完
 0