理解了 1+2的过程,就理解了Java虚拟机

云计算 虚拟化
在面试的时候,在问到关于JVM相关的问题,会发现不少的面试者都是机械的在记忆,稍一细问就戛然而止。属于死记硬背型的,估计是看书里记个大概的概念或者图,并没有理解含义。

[[322423]]

在面试的时候,在问到关于JVM相关的问题,会发现不少的面试者都是机械的在记忆,稍一细问就戛然而止。属于死记硬背型的,估计是看书里记个大概的概念或者图,并没有理解含义。

实际上这块内容,看概念的时候能对照一个简单的程序分析,可以更好的理解。下面咱们开始。

市面上常见的JVM书籍里,关于JVM的体系结构,一般划分成以下几个部分:

  1. 类加载器
  2. 程序计数器(Program Counter Register,简称PC Register)
  3. Java 虚拟机栈(Java Virtual Machine Stacks)
  4. 堆(Heap)
  5. 方法区(Method Area)
  6. 运行时常量池(Run-time Constant Pool)
  7. 本地方法栈(Native Method Stack)
  8. 栈帧(Stack Frame)
  9. 执行引擎(Execution Engine)

其中2~7项,又称为运行时数据区,毕竟这些东西只有JVM 跑起来才会创建。这个分类,基本都是参照 Java 虚拟机规范。

如果干巴巴的记概念没啥意思,吃个饭可能就忘了。接下来用 1+2这个程序来试着理解它。

我们来看个初学Java 编程的时候都基本都写过的,类似 Hello World的程序。

  1. public class HelloWorld { 
  2.     public static void main(String[] args) { 
  3.         int a = 1; 
  4.         int b = 2; 
  5.         int c = a + b; 
  6.     } 

先 javac 编译之后,再用javap -verbose HelloWorld 来观察一下, 你会看到类似下面的输出内容:

  1. public class HelloWorld 
  2.   minor version: 0 
  3.   major version: 55 
  4.   flags: (0x0021) ACC_PUBLIC, ACC_SUPER 
  5.   this_class: #2     // HelloWorld 
  6.   super_class: #3    // java/lang/Object 
  7.   interfaces: 0, fields: 0, methods: 2, attributes: 1 
  8. Constant pool: 
  9.    #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V 
  10.    #2 = Class              #13            // HelloWorld 
  11.    #3 = Class              #14            // java/lang/Object 
  12.    #4 = Utf8               <init> 
  13.    #5 = Utf8               ()V 
  14.    #6 = Utf8               Code 
  15.    #7 = Utf8               LineNumberTable 
  16.    #8 = Utf8               main 
  17.    #9 = Utf8               ([Ljava/lang/String;)V 
  18.   #10 = Utf8               SourceFile 
  19.   #11 = Utf8               HelloWorld.java 
  20.   #12 = NameAndType        #4:#5          // "<init>":()V 
  21.   #13 = Utf8               HelloWorld 
  22.   #14 = Utf8               java/lang/Object 
  23.   public HelloWorld(); 
  24.     descriptor: ()V 
  25.     flags: (0x0001) ACC_PUBLIC 
  26.     Code: 
  27.       stack=1, locals=1, args_size=1 
  28.          0: aload_0 
  29.          1: invokespecial #1                  // Method java/lang/Object."<init>":()V 
  30.          4: return 
  31.       LineNumberTable: 
  32.         line 1: 0 
  33.  
  34.   public static void main(java.lang.String[]); 
  35.     descriptor: ([Ljava/lang/String;)V 
  36.     flags: (0x0009) ACC_PUBLIC, ACC_STATIC 
  37.     Code: 
  38.       stack=2, locals=4, args_size=1 
  39.          0: iconst_1 
  40.          1: istore_1 
  41.          2: iconst_2 
  42.          3: istore_2 
  43.          4: iload_1 
  44.          5: iload_2 
  45.          6: iadd 
  46.          7: istore_3 
  47.          8: return 
  48.       LineNumberTable: 
  49.         line 3: 0 
  50.         line 4: 2 
  51.         line 5: 4 
  52.         line 6: 8 

好嘞。咱们都知道,上面这些就是Java的字节码。有了上面这个输出的内容,你把自己想像成虚拟机,来运行它,就理解了 Java 虚拟机里各个部分了。

首先,这部分内容,要执行,一定得先读到内存里,负载读这些内容的,就是虚拟机的类加载器。

加载进来的其实是个二进制流,然后呢,需要把它整理成对应格式的内容才方便使用嘛。比如这个类叫啥名字,继承了谁,都有什么方法,方法名字叫啥,内容是什么这些东西要找个地方放着。放哪好呢?方法区就是干这个的。

所谓的运行时常量池也是方法区里的一块区域。往上看Constant Pool 在运行时会被解析成 Run-time Constant Pool。如果涉及到对其他类的引用等等,会在加载之后再链接的时候,把这里面的符号引用转化成直接引用。

另外一些部分呢?概括来讲就是Java虚拟机栈,就是咱们常说的栈,是用来执行方法里的具体内容的。这一部分其实可以这样理解。Java 虚拟机,和我们真实的物理机类似,都会把程序提供的指令执行,只不过虚拟机是一个提供了一套有限指令集的软件。物理机基本都是基于寄存器执行,而 Java 虚拟机的对于指令的执行实现是基于栈的。

既然是栈,那栈里要放点什么?没错,是栈帧,英文是 Frames,就是咱们在使用 IDE debug 的时候看到的那一层一层的内容。

 

每个方法调用的时候,都会出现一帧,每一帧也是个结构,方法执行用到的东西都在里面。比如在 Debug 的时候,一般都会看到每个变量和值, 这些变量称为本地变量(local variables),在上面的输出内容里也有stack=2, locals=4, args_size=1 我们看到locals就是本地变量,args_size是方法参数的长度,还有一个就是操作数(stack),数值是栈的最大深度。

每个class 的任意一个方法里,都会有 frame ,它们都有自己的 local variables 本地变量表, 自己的operand stack 操作数栈,以及到run-time constant pool 运行时常量池的引用。当然,也可以有一些扩展信息,例如debug info。

那具体上面简单的一个 1+2 这个操作,对应到 jvm 指令有这些:

  1. 0: iconst_1 
  2.          1: istore_1 
  3.          2: iconst_2 
  4.          3: istore_2 
  5.          4: iload_1 
  6.          5: iload_2 
  7.          6: iadd 
  8.          7: istore_3 
  9.          8: return 

具体当前执行到第几条指令,需要有个标识,这个活儿让程序计数器给干了。这小子一直指向下一条即将执行的指令。基本栈的实现,上面的指令大意是把常量1赋值给第一个变量,常量2赋值给第二个变量,之后,变量一入栈,变量二入栈,执行iadd操作的时候,这两个数据出栈,完成求和,再赋值给变量3,入栈,再返回。下次咱们细说JVM指令的时候,再详细说说。这些指令的执行,当然离不开执行引擎。

因为不需要执行Native方法,所以我们一般不用本地方法栈,这是给类似JNI这些本地方法实现准备的。

你看,观察了1+2的过程,基本Java 虚拟机的结构是不是就理解了?:-) 如果还是记不住的话,你可以这样想啊,Java 的世界里,经常会说到堆和栈。那栈用来存啥呢?想想你 debug 时候看到的那一层层的帧, 然后再想想今天的1+2的执行,应该就齐了。

本文转载自微信公众号「 Tomcat那些事儿」,可以通过以下二维码关注。转载本文请联系 Tomcat那些事儿公众号。

 

 

责任编辑:武晓燕 来源: Tomcat那些事儿
相关推荐

2022-05-03 00:03:11

状态管理前端开发

2022-03-27 09:06:25

vuexActionsMutations

2019-09-29 06:12:38

交换机配置vlan

2024-03-15 08:23:26

异步编程函数

2019-12-26 09:15:44

网络IOLinux

2024-11-25 07:39:48

2022-10-20 18:43:32

C语言golang安全

2018-03-21 16:19:40

MVCMVPMVVM

2012-11-30 11:19:02

JavaScript

2022-07-27 22:59:53

Node.jsNest

2019-09-16 08:32:59

递归算法编程

2012-11-14 09:57:46

JavaJava虚拟机JVM

2019-09-18 10:12:37

递归数据结构

2019-07-24 16:04:47

Java虚拟机并发

2024-03-29 11:42:21

Java虚拟机

2019-08-27 16:23:41

Docker虚拟化虚拟机

2022-11-26 00:22:14

引用类型数组

2009-08-18 22:06:59

VMware虚拟机软件

2024-04-03 13:49:00

Java虚拟机方法区

2024-03-26 07:30:07

Java虚拟机源文件
点赞
收藏

51CTO技术栈公众号