前言
前段时间重新系统的复习了jvm相关知识,但是jvm知识中,理论知识偏多,很难通过写代码跑Demo的方式加强印象,因此总感觉无法掌握jvm整体知识框架。
此文中笔者从java类的角度出发,梳理类在jvm中的生命流程。因此,此篇文章不会展开过多的细节,力争从全局视角来表达笔者对jvm整体知识框架的理解
正文
在学习jvm时,总是会学习到下面一张经典的图
图中画出了jvm运行时数据区域中重要部分,但是笔者在看到这张图时,仍然无法对类在jvm中的生命流程有一个感性的认识,因为这张图是从某个时间点着眼,对jvm运行时数据区域做了一个剖面。
因此,基于上面这张图,笔者站在巨人的肩膀上,结合自己的思考,画了下面的图
此图展示出了jvm中几个主要的知识点:
- jvm支持多语言
- jvm可以通过多种外部源加载字节码
- 虚拟机栈、堆、方法区中的数据
jvm支持多语言
对于jvm来说,它只会加载字节码数据。只要外部源提供的字节码数据符合jvm定义的class文件格式,就可以在jvm中加载运行。因此除了我们常见的java语言外,现在很多其它的语言,如kotlin、scala等都可以在jvm中运行
jvm可通过多种外部源加载字节码
在日常开发中,接触到的最多的就是:开发人员写了一个java文件,然后将其编译为class文件,然后启动jvm加载运行该class文件。
但是如前文提到的,对于jvm来说,只要加载的数据符合字节码格式规范,就可以在jvm中加载运行。
因此我们还可以从网络IO流中获取字节码数据,或者使用工具直接动态生成字节码数据。Spring中的切面就是使用了CGlib来动态生成字节码,从而实现动态代理能力
同时,与加载字节码过程息息相关的就是类加载器了。
关于类加载器的经典图示如上。启动类加载器与扩展类加载器的目的是加载jdk中内置的类,避免开发人员自己写了一个与jdk内置类一样的类(比如开发人员自己写了一个java.lang.String,想覆盖掉jdk内置的String),从而影响jvm的运行。
当我们需要自己指定字节码文件的加载源时,就可以实现一个自定义类加载器。除此之外,开发人员还可以按需对字节码做一些额外的操作,比如在生成字节码时进行加密,然后在自定义类加载器中加载字节码时再进行解密,以此来实现代码加密。
在如下的类的生命周期流程中,类加载器只涉及加载阶段
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
在初始化阶段后,类的相关信息就已经加载到了方法区中。
虚拟机栈、堆、方法区中的数据
虚拟机栈时线程私有的,即一个线程对应着一个虚拟机栈。虚拟机栈中存放的时栈帧,一个栈帧对应着一个方法。栈帧中有两个重要的部分:局部变量表和操作数栈
方法区中主要存放着类相关信息及常量字符串(包括静态的和程序运行过程中动态生成的)
堆中就是存放着创建出来的实例对象。堆区域应该是开发人员日常工作直接接触最多的区域了,比如我们最常见的OOM问题,就大概率会发生在这个区域。也因此我们会需要GC来清理堆中的不再需要的对象,避免堆的OOM
而在GC时,使用的可达性分析算法中,首要就是从GCRoots开始遍历。在GCRoots遍历时,一个重要的Root就虚拟机栈帧中的局部变量表。
参考
写在最后
笔者是一名博客新手,帖子有错误或不合理之处,欢迎大佬们批评指正。 :)