一.Java从编译到运行的过程

1.图示(JDK,JRE,JVM三者关系)

JVM概念及虚拟机栈——JVM(1)_java


​java​​​文件经过​​javac​​​的编译后形成了​​class​​字节码文件,然后送入JVM在JRE的类库的帮助下进行类加载。从这张图也可以看出JDK,JRE,JVM之间的关系。

2.三者关系

  • JVM: JVM只是一个翻译,把Class翻译成机器识别的代码,但是需要注意,JVM 不会自己生成代码,需要大家编写代码,同时需要很多依赖类库,这个时候就需要用到JRE。
  • JRE: JRE是什么,它除了包含JVM之外,还提供了很多的类库(就是我们说的jar包,它可以提供一些即插即用的功能,比如读取或者操作文件,连接网络,使用I/O等等之类的)这些东西就是JRE提供的基础类库。JVM 标准加上实现的一大堆基础类库,就组成了 Java 的运行时环境,也就是我们常说的 JRE(Java Runtime Environment)。
  • JDK: 但对于程序员来说,JRE还不够。我写完要编译代码,还需要调试代码,还需要打包代码、有时候还需要反编译代码。所以我们会使用JDK,因为JDK还提供了一些非常好用的小工具,比如​​javac​​​(编译代码)、​​javap​​(反编译)等。这个就是JDK。

二.JVM是一种规范

为什么说JVM是一种规范呢?有以下两个原因

①JVM的跨平台性

同一个Java程序,可以在多个不同的操作系统上执行,不受平台的影响(当然前提是你安装了属于此平台的JDK)。比如安装了windows版本的JDK,我的Java程序就可以在windows操作系统上面运行。

②JVM的语言无关性

JVM识别的就是class文件,不管你是什么语言,Java也好,kotlin也好,还是你自己创的语言也好,只要能编译成class文件,JVM就能识别并进行操作。

综上,我们说JVM是一种规范。

常见的JVM实现有

JVM概念及虚拟机栈——JVM(1)_jdk_02


我们用的一般就是Hotspot

下面我们看一下JVM是怎么规范的

三.JVM对内存的规范

JVM在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,也就是对内存进行规范化。我们把进行规范化的这块区域叫做运行时数据区域。里面包含的内容如图所示

JVM概念及虚拟机栈——JVM(1)_本地方法_03


根据是否为线程共享,分成两部分。

一部分是线程共享区,里面有方法区(方法区在hotspot中有时被称为永久代、元空间)和,另一部分是线程私有区,里面有虚拟机栈本地方法栈程序计数器

除了运行时数据区域之外,还有没经过虚拟化的直接内存,也就是没经过规范化的内存。这部分也可以被Java程序所使用。比如电脑内存是16G,JVM占用了5G,那么剩下的11G就被称为直接内存。但是这部分使用起来不太方便。

1.虚拟机栈讲解

①概念

存储当前线程运行java方法所需的数据,指令,返回地址。有栈帧这个概念,一个方法就是一个栈帧。

②实例演示

JVM概念及虚拟机栈——JVM(1)_本地方法_04


比如这一段程序,是在​​main​​​方法中​​A​​​调用​​B​​​,然后​​B​​​调用​​C​​。那么在程序执行的过程中,虚拟机栈发生的变化是这样的。

首先main方法是主线程,我们命名为线程1,执行main方法的时候,main方法会作为一个栈帧,进入虚拟机栈内

JVM概念及虚拟机栈——JVM(1)_main方法_05

然后main方法调用A方法,A方法作为另一个栈帧进入虚拟机栈

JVM概念及虚拟机栈——JVM(1)_本地方法_06

以此类推

JVM概念及虚拟机栈——JVM(1)_java_07


JVM概念及虚拟机栈——JVM(1)_java_08


虚拟机栈还可以进行大小设置,默认值取决于平台,具体怎么设置不用管。

这样的话就掌握了虚拟机栈和栈帧的概念了

2.栈帧里面有什么

JVM概念及虚拟机栈——JVM(1)_main方法_09


一般来讲有四个,分别是局部变量表,操作数栈,动态连接(这个先不管),完成出口

  • 先解释下程序计数器吧,它指向当前线程正在执行的字节码指令的地址

我们换一张图,继续对栈帧进行细致的讲解

JVM概念及虚拟机栈——JVM(1)_main方法_10


演示程序如下

JVM概念及虚拟机栈——JVM(1)_jdk_11


这一部分演示程序非常简单,就不再解释了。我们之前说,JVM针对的是字节码文件即class文件,所以我们展示下上述代码编译后形成的class文件

我们只看​​work​​方法的红框部分

JVM概念及虚拟机栈——JVM(1)_本地方法_12


最左边的数字,后面带个冒号的那些数字,是字节码的地址,也就是字节码的偏移量,程序计数器保存的就是这个值。比如程序计数器保存着1,那么下一步就执行​​istore_1​​这个指令。OK,下面让我们来详细演示下这个过程。

①iconst_1、istore_1

JVM概念及虚拟机栈——JVM(1)_jdk_13


JVM概念及虚拟机栈——JVM(1)_java_14


后面两步和前两步一样.

②iconst_2、istore_2

JVM概念及虚拟机栈——JVM(1)_main方法_15

③然后执行iload_1、iload_2

JVM概念及虚拟机栈——JVM(1)_java_16

④iadd

JVM概念及虚拟机栈——JVM(1)_本地方法_17

⑤bipush 10

JVM概念及虚拟机栈——JVM(1)_jdk_18

⑥imul

这个也是分了两步

JVM概念及虚拟机栈——JVM(1)_jdk_19


后面两步就是把这个30存到局部变量表,然后又取出来,最后通过完成出口完成,并返回这个30。

完成出口是调用此方法的那个位置,比如这里是在​​main​​方法的第三行调用的此方法

JVM概念及虚拟机栈——JVM(1)_main方法_20


那么完成出口可能就记3,然后方法返回的时候就通过3来返回到方法调用的位置,继续向下执行

OK,以上就是通过一段程序,来对栈帧里面的结构进行的介绍。

3.本地方法栈

本地方法栈跟 Java 虚拟机栈的功能类似,Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。但本地方法并不是用 Java 实现的,而是由 C 语言实现的(比如​​Object.hashcode​​​方法)。
本地方法栈是和虚拟机栈非常相似的一个区域,它服务的对象是 ​​​native​​ 方法。你甚至可以认为虚拟机栈和本地方法栈是同一个区域。HotSpot直接把本地方法栈和虚拟机栈合二为一 。

4.运行时数据区中的其他区域

JVM概念及虚拟机栈——JVM(1)_jdk_21


除了刚刚讲的线程私有区和直接内存,还有方法区和堆。那么一段程序执行后,某些代码在哪些区域是怎么划分的呢?看下面的代码

JVM概念及虚拟机栈——JVM(1)_jvm_22

  • ​age​​​和​​sex​​都是静态变量或者常量,这些是跟随着类的,会在方法区里面。
  • 后两个​​object​​​和​​isKing​​​是成员变量,是跟随着对象的,等对象​​new​​​出来之后他们才有,而对象是在堆里面​​new​​的,所以他们两个也在堆里面。
  • ​main​​​方法里面的​​x​​​和​​y​​​还有​​lobject​​都是局部变量,会在局部变量表里面。

四.深入理解JVM内存处理

实例代码

JVM概念及虚拟机栈——JVM(1)_java_23


它运行的时候,内存处理的流程是

  • ①JVM申请内存
    ②初始化运行时数据区
    ③类加载
    ④执行方法
    ⑤创建对象

前三步完成以后,会是这个样子

JVM概念及虚拟机栈——JVM(1)_本地方法_24


最后创建对象,是这样子

JVM概念及虚拟机栈——JVM(1)_jdk_25


那么对象创建了,如何进行回收呢?下篇文章将会详细讲解垃圾回收机制,现在我们只需要知道,堆可以分成两部分,新生代和老年代,新生代又可以分成三部分

JVM概念及虚拟机栈——JVM(1)_java_26

如图,蓝色的是新生代,橙色的是老年代。一个对象经过多次垃圾回收后都没被回收,那么它就会进入老年代。
了解到这里,就够了。

了解:JHSDB工具的使用