漫画JVM(一)JVM整体概述——从Java代码到内存中的对象

公众号:爱码叔漫画软件设计(搜:爱码叔)

CSDN: https://blog.csdn.net/liyiming2017

知乎:https://www.zhihu.com/people/zhong-guo-ren-83-43

专注于软件设计与架构、技术管理。擅长用通俗易懂的语言讲解技术。对技术管理工作有自己的一定见解。欢迎大家关注我!

Java 作为面向对象语言,开发者使用它来设计一个个对象,完成程序的开发。开发完的程序便是一个个 java 文件。而真正运行在计算机中的,并不是Java文件(也不是字节码文件),而是处于内存中的对象,

内存,是程序世界的地球,所有的对象都居住、工作在这里。没有在内存中 “入住” 的对象,无论是存在于 Java 文件还是 class 文件中,都是纸上谈兵,只能称之为对象的定义,或者说是对象的设计“图纸”。

那么 .java 文件中定义的对象是如何变成内存中的对象,又是如何在电脑中运行起来的呢?这就得看今天的主角—— JVM。此外,对象有生产就会有消亡,我们世界的生物不断出生、消亡,重复这个轮回。在Java的世界也是如此。

JVM的核心职责如下:

  1. 根据 .Java文件中的对象定义在内存中创建对象。
  2. 让对象根据 .Java 文件中定义的行为 “动” 起来,也就是执行对象方法。
  3. 清理没用的对象。也就是耳熟能详的垃圾回收。

本文将聚焦在对象创建和方法执行的宏观过程和概念,最后引出Java内存区域。垃圾回收在以后的文章中再做讲解。

下图是JVM工作时所使用的,构造Java世界的 “办公桌”,也就是 JVM 管控的内存区域。右下角便是Java对象的世界。本文最后会对此图详细讲解。咱们先继续向下看。

alt text

Java对象从设计到生产

JVM并不能凭空生产出一个对象。我们生活的世界同样如此,制造一个复杂的东西,需要精准的图纸。例如挖一口井。图纸上会标注挖多深,井的直径是多少,所使用的砖的规格等等。讲到这里,我突然想起一个笑话,本来要挖井,但是把设计图拿反了,盖了一个烟囱出来。总之,图纸是生产制造必不可少的东西,“制造对象” 也是如此。

“java编码”的图纸

java 文件,便是程序员生产出来的对象设计图纸。不过 JVM 并不是直接使用 java 图纸来生产对象。原因是 java 文件中的文法结构,对开发者友好,有助于提升开发效率,但是对于 JVM 并不友好。JVM 直接使用的效率并不高。

如果一张 “图纸” 无法同时让设计者和使用者都满意,那么就别生往一块凑了。搞两种格式的设计图,但是表示同样的设计不就OK了?于是便有了 “字节码编码” 的图纸。

alt text

“字节码编码”的图纸

class 文件也叫字节码文件,用来存储字节码。字节码是 JVM 用起来更为方便的设计图。为了和java 文件表示同样的设计,字节码通常由 java 文件转化而来。这就是常说的编译。

字节码图纸才是 JVM 能够读懂的图纸。

内存中的图纸

JVM 虽然能够读懂字节码图纸,但是干起活来,直接使用字节码图纸的效率还是很低。因此JVM需要布置一下自己的工作台,它将字节码图纸大卸八块,按照自己工作的需要将图纸一块块放置到自己的工作台(JVM内存方法区)。这样JVM在工作时候,可以做到想看图纸哪个部分,立刻就能找到。比埋头在一张巨大的图纸中去搜寻方便多了。

把字节码图纸 “大卸八块” 放到内存里的过程便是 JVM 的类加载。加载完成后,会生成代表整张图纸(类)的 java.lang.class 对象,通过它可以访问图纸中的各种数据。

总结一下,JVM将 字节码图纸描述的“结构”,进行转换后搬到了内存之中,生成 java.lang.class 对象。JVM创建对象时,使用的是内存中的这张结构化的图纸。

从图纸到对象

程序想要运行起来,需要真的创建出对象,而不能只有存在于图纸中的对象。JVM 会根据内存中的图纸创建出真正的对象,这些对象会进入 “Java的世界” 开始自己的一生,它们的家就是 JVM堆内存。

小结

想要制造对象,首先要开发设计图。Java中的对象设计图,经历了Java 图纸(人类可读)、字节码图纸(JVM可读)、内存中的图纸(JVM可用)。

当图纸进入内存后,JVM便能使用图纸随心所欲的制造对象。

alt text

概念理解类比:

人类可读的对象设计图纸:java文件

JVM 可读的对象设计图纸:字节码文件或者流

JVM 可以直接使用的对象设计图纸:以 java.land.class 对象作为代表的,加载到内存中的类

JVM 放置类图纸的工作台:JVM方法区

JVM 安置生产出来的对象的场所:JVM堆

Java对象如何干活

我们已经了解了 Java 对象如何在内存中被创建出来。不过仅仅是创建对象没有任何意义,对象需要能 “动” 起来,才有价值,否则就是一个花瓶,白白浪费寸土寸金的内存空间。

所谓的 “动”,就是对象的方法可以被执行。方法根据特定的输入,按照程序所写逻辑,计算出结果,返回给调用方。

真正的运算,其实是由CPU所承担,程序只负责定义逻辑。我可以打个比方,一位学生在解一道(1+2)*(2+3)的运算题,他手里有一台计算器帮助他运算。我们可以用以下类比。

  1. 他的解题思路以及他所掌握的运算顺序,就是程序方法的逻辑代码。
  2. 每一步运算用计算器进行计算,比如1+2。计算器就是CPU。
  3. 由于涉及到多步计算,中间的计算结果,例如1+2、2+3的得数,需要临时记录在草稿纸上,草稿纸就是栈帧。
  4. 学生在解题过程中需要知道自己计算到哪一步了。他可能不会记录在纸上,但一定记在脑子里。程序则需要记在内存里,这块内存区域叫做程序计数器

这里出现了两个新的概念,栈帧和程序计数器。

程序计数器——JVM执行指令的指示器

我们先看程序计数器,它比较简单。程序计数器是内存中非常小的一块空间,用来记录当前线程执行到的字节码行号。

Java 通过切换 CPU 执行的线程,来实现多线程。在某一个时间点,一个 CPU 或者内核,只能执行某一条线程中的指令,因此当线程切换时,需要程序计数器来恢复线程,才能接着向下执行。

即使在同一个线程中,字节码解释器在工作的时候,也需要程序计数器来取得下一条要执行的字节码指令。

程序计数器可以看做是程序执行时,字节码指令的指针。它也是JVM管理的一块内存。

栈帧——方法运行时的草稿纸

我们再来看栈帧。对象的方法在调用过程中,还会调用到其他对象的方法,获得返回值后继续原方法的调用。后面被调用到的方法,会被先执行完。JVM使用栈结构来组织运算的过程,帧中的每个元素是一个栈帧。一个栈帧代表一个方法。只有栈顶的帧正在运行。调用新方法时,会把新方法的栈帧入栈。出栈后,新的处于栈顶的栈帧接棒运行。

栈帧可以看作是对象工作时,记录中间数据的草稿纸。内存中有专门的区域用于存放帧栈,叫做JVM栈。每个线程会有一个独立的栈,可以认为一个JVM栈是一个草稿纸箱子。每个方法执行的时候,分配一张新的草稿纸,草稿纸后进先出。

一个栈帧代表一个方法,栈帧数据结构主要包括如下数据:

  1. 局部变量表。存储方法中定义的局部变量。
  2. 操作数栈。存储一次计算所需要的操作数,例如计算100+200,JVM需要先将100和200压入操作数栈。执行加法操作时,会将栈顶两个元素取出相加。
  3. 动态链接。每个栈帧都包含指向常量池该栈帧所属方法的引用。
  4. 返回地址。保存当前方法执行结束时,要去往的地址,例如调用它的方法的程序计数器所指示的位置

下图左侧为JVM栈的结构。右下是基于栈帧结构执行一个简单方法的示例。

alt text

小节

方法在执行时,JVM先构造该方法的栈帧。栈帧是方法执行过程中所需要的数据结构以及存储空间。栈帧中的局部变量表用来保存局部变量,操作数栈用来完成基于栈的指令集计算(操作数栈和方法栈帧是两个不同的概念,不要混淆)。

方法的调用和返回,其实就是栈帧的进栈和出栈,只有栈顶栈帧对应的方法处于执行状态。

程序计数器告诉 JVM 需要执行哪一条字节码指令。

Java的内存区域

以上,概述了Java对象的创建、对象中方法的执行。上文已经提到了几种Java的内存区域,我先做个总结。

  1. 方法区。线程共享,存储“类图纸”,也就是从class文件加载而来的类型信息。此外还用来保存常量、静态变量。
  2. 堆内存。用于存储Java对象
  3. 程序计数器。线程私有,记录所执行的字节码指令行号。
  4. JVM栈。线程私有,每个方法对应一个栈帧,保存方法中的局部变量,以及为字节码指令提供操作数。

此外,Java内存中还有本地方法栈。它和 JVM 栈的作用类似,只不过 JVM 用本地方法栈来执行本地方法(Native Methhod)。甚至在Hostspot虚拟机中,本地方法栈和JVM栈被合二为一。

这几块区域如下图所示。

alt text

总结

本章从宏观上概述了 JVM 虚拟机的两个主要工作,一是 JVM 如何创建对象,二是如何执行对象中的方法。

对象的创建,从它的设计图纸开始。对象设计图纸经历了一步步地演变,从 “Java图纸” 到 “字节码图纸”,最终成为加载到内存中的 “类型图纸”。JVM在运行时,根据内存中的类型图纸创建对象。类型图纸保存在内存的方法区,对象保存在内存堆。

方法的执行,依赖于栈帧结构。Java使用栈帧保存局部变量,并提供操作数栈,以让JVM完成基于栈的指令集执行。栈帧所处于的栈,则是实现了方法间的层次调用,串起一个线程从头到尾的方法调用。JVM对方法的执行主要使用程序计数器和JVM栈这两块内存区域。

All rights reserved
Except where otherwise noted, content on this page is copyrighted.