java类在jvm中的生命流程

106 阅读4分钟

前言

前段时间重新系统的复习了jvm相关知识,但是jvm知识中,理论知识偏多,很难通过写代码跑Demo的方式加强印象,因此总感觉无法掌握jvm整体知识框架。

此文中笔者从java类的角度出发,梳理类在jvm中的生命流程。因此,此篇文章不会展开过多的细节,力争从全局视角来表达笔者对jvm整体知识框架的理解

正文

在学习jvm时,总是会学习到下面一张经典的图

image-20241020112359747

图中画出了jvm运行时数据区域中重要部分,但是笔者在看到这张图时,仍然无法对类在jvm中的生命流程有一个感性的认识,因为这张图是从某个时间点着眼,对jvm运行时数据区域做了一个剖面。

因此,基于上面这张图,笔者站在巨人的肩膀上,结合自己的思考,画了下面的图

image-20241020113109675

此图展示出了jvm中几个主要的知识点:

  1. jvm支持多语言
  2. jvm可以通过多种外部源加载字节码
  3. 虚拟机栈、堆、方法区中的数据

jvm支持多语言

对于jvm来说,它只会加载字节码数据。只要外部源提供的字节码数据符合jvm定义的class文件格式,就可以在jvm中加载运行。因此除了我们常见的java语言外,现在很多其它的语言,如kotlin、scala等都可以在jvm中运行

jvm可通过多种外部源加载字节码

在日常开发中,接触到的最多的就是:开发人员写了一个java文件,然后将其编译为class文件,然后启动jvm加载运行该class文件。

但是如前文提到的,对于jvm来说,只要加载的数据符合字节码格式规范,就可以在jvm中加载运行。

因此我们还可以从网络IO流中获取字节码数据,或者使用工具直接动态生成字节码数据。Spring中的切面就是使用了CGlib来动态生成字节码,从而实现动态代理能力

同时,与加载字节码过程息息相关的就是类加载器了。

image-20241020121437270

关于类加载器的经典图示如上。启动类加载器与扩展类加载器的目的是加载jdk中内置的类,避免开发人员自己写了一个与jdk内置类一样的类(比如开发人员自己写了一个java.lang.String,想覆盖掉jdk内置的String),从而影响jvm的运行。

当我们需要自己指定字节码文件的加载源时,就可以实现一个自定义类加载器。除此之外,开发人员还可以按需对字节码做一些额外的操作,比如在生成字节码时进行加密,然后在自定义类加载器中加载字节码时再进行解密,以此来实现代码加密。

在如下的类的生命周期流程中,类加载器只涉及加载阶段

加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载

初始化阶段后,类的相关信息就已经加载到了方法区中。

虚拟机栈、堆、方法区中的数据

虚拟机栈时线程私有的,即一个线程对应着一个虚拟机栈。虚拟机栈中存放的时栈帧,一个栈帧对应着一个方法。栈帧中有两个重要的部分:局部变量表和操作数栈

方法区中主要存放着类相关信息及常量字符串(包括静态的和程序运行过程中动态生成的)

堆中就是存放着创建出来的实例对象。堆区域应该是开发人员日常工作直接接触最多的区域了,比如我们最常见的OOM问题,就大概率会发生在这个区域。也因此我们会需要GC来清理堆中的不再需要的对象,避免堆的OOM

而在GC时,使用的可达性分析算法中,首要就是从GCRoots开始遍历。在GCRoots遍历时,一个重要的Root就虚拟机栈帧中的局部变量表。

参考

深入理解Java虚拟机(第3版)

尚硅谷JVM精讲与GC调优教程

写在最后

笔者是一名博客新手,帖子有错误或不合理之处,欢迎大佬们批评指正。 :)