目录
JVM章节主要是面试导向的,纯八股文
在JVM中,面试中最多出的是这三个方面
- JVM内存区域划分
- 类加载机制
- 垃圾回收机制
接下来,我们来进一步了解这三个方面
JVM内存区域划分
- JVM(也就是Java虚拟机)是仿照真实机器,真实的操作系统来设计的
- 在真实的操作系统中,对进程的地址空间进行了分区域的设计
- JVM也就仿照了操作系统的情况,进行了分区域的设计
JVM从操作体系中申请到了一些内存空间
- JVM空间划分,就是把申请到的内存空间,按照功能分成不同的小区域
JVM具体是怎么划分的(四个核心区域)
程序计数器
- 程序计数器是一个很小的区域,用来记录 当前指令执行到哪了
- 类似计算机组成原理里的 PC寄存器,但PC寄存器是CPU里的东西
元数据区
- 元数据区是用来保存 当前类 已经加载好的数据的
- Java代码 =>(编译成)class文件 => 加载到内存中
- 想要运行这Java代码,就要把.class加载到内存中(这就是类加载的过程)
- 元信息,指的就是一些属性
- 比如。类叫什么名字,是不是public,继承自哪些类,实现了哪些接口......
栈
- 栈是用来保存方法的调用关系的
- 每次调用方法时,就会进入方法内部执行,当方法执行完后,返回到调用位置,继续往后走
- 栈 后进先出的特点 很适合表示函数的调用关系
例如 main函数中调用test1函数
- 栈帧内会放着 函数要用到的参数,局部变量,返回值返回的地址(test1结束后,继续执行哪里)
- 栈 这个空间不算很大,一般就是几MB,十几MB这样(可以通过JVM的启动参数来配置)
堆
- 堆 -> 保存 new对象
- 堆是JVM中最大的空间区域了
- 往集合类中添加元素,就是放到堆中
- 如果堆中的对象,不再使用了的话,就会被释放掉(垃圾回收)
- 堆中会有 比如方法叫啥名字,参数有几个,都叫啥,都是啥类型,返回值是啥类型
举个栗子~
- 因为Test()是一个被new的对象 ,所以会被放在堆中
- 如果a是一个局部变量 ,那就放到栈中
- 如果a是一个成员变量 ,那就放到堆中
- 如果a是一个静态成员变量 ,那就放到元数据区中
整个Java进程共用一份元数据区和堆
一个Java进程 中可能会有多份程序计数器和堆(每个线程都有一份)
注意区分JVM中的栈、堆 和 数据结构的栈、堆 !!!
- JVM中的栈、堆 和 数据结构的栈、堆没啥关系
- 虽然概念相似,但不是一个东西
JVM类加载
类加载本身是一个复杂的过程
在面试中,类加载主要关心两个方面
方面一:类加载步骤有哪些
三个大的阶段,其中第二个阶段,又分成三个步骤,所以一共是五个步骤
1.加载
- 找到.class文件(因为类信息在.class文件中)
- 根据类的全限定名(包名 + 类名,形如java.lang.String)
- 打开文件,把文件读取到内存中(还不涉及解析)
2.验证
- 解析,校验.class文件中读到的内容是否合法 ,若合法,就把**.class文件中的内容转成结构化的数据**
- 虽然.class是一个二进制文件,但是格式是有明确要求的
3.准备
- 给类对象申请内存空间
- 内存空间初始化,就是一个全"0"的空间
4.解析
- 针对字符串常量进行初始化
- 字符串常量本身就包含子在.class文件中
- 这一步就是要把 .class文件中解析出来的字符串常量放到 元数据区的常量池中
5.初始化
- 针对上一步解析出来 的类对象进行最终 的初始化
- 填充类对象的属性(包含 类中的静态成员)
- 如果这个类还有父类,并且父类还没加载,此环节也会触发父类的类加载
方面二:类加载中的"双亲委派模型"(超高频考点)
- 双亲委派模型描述了 类加载中,根据全限定类名,找到.class文件的过程
- 个人理解的话,觉得"父亲委派模型"这个名字更贴切
JVM默认提供了三种类加载器:
|------------------------|-------------------|-------------------------------------|
| 类名 | 辈分 (作者自己加的,帮助理解) | 类来自哪里 |
| BootstrapClassLoader | 爷(爹继承爷) (爷继承null) | Java标准库的目录 |
| ExtensionClassLoader | 爹(子继承爹) | Java扩展库的目录 (对标准库做的扩充) |
| ApplicationClassLoader | 子 | Java的第三方库/ 当前项目 (通过Maven下载来的都是第三方库) |
[三种类加载器]双亲委派模型的过程
- 在进行类加载时,通过全限定类名,去找.class时
- 把(子)ApplicationClassLoader作为入口开始往上一辈传递
- (子)ApplicationClassLoader把 找目标.class 的任务委派给它爹 (爹)ExtensionClassLoader
- (爹)ExtensionClassLoader 也把 找目标.class 的任务委派给它爹 (爷)BootstrapClassLoader
- (爷)BootstrapClassLoader也把 找目标.class 的任务委派给它爹,但是 爷的上一辈是null,发现爷没有爹了o(╥﹏╥)o
- 没办法,(爷)BootstrapClassLoader只能在自己的Java标准库中找这个 .class
- 要是(爷)BootstrapClassLoader 没找到,就把这个找.class 的任务交给它儿子,也就是(爹)ExtensionClassLoader
- (爹)ExtensionClassLoader也在自己的Java扩展库中找这个 .class
- 要是(爹)ExtensionClassLoader没找到,就把这个找.class 的任务交给它儿子,也就是(子)ApplicationClassLoader
- (子)ApplicationClassLoader也在自己的Java第三方库中找这个 .class
- 在上面爷、爹、子的任一阶段,要是找到了目标.class,就把类加载出来,要是没找到就报错
哈哈^_^,是不是看迷糊了,作者我小小概括一下(编个故事,别当真哈,逻辑差不多是这个意思就行)
- 出于对长辈的尊重
- 找.class似乎是 类加载器 家族中 无上荣誉的事情
- "子"捧着.class 交给"父"
- "父"捧着.class 交给"爷"
- "爷"因为头顶上只有null了,所以就开始从自己的 "私库"中找有没有对应的.class,如果有,就掏出来并进行类加载,如果没有,就让自己的儿子,也就是"父"来找
- "父"、"子"同上
- 如果在任一层找到了目标.class,就直接加载,要是"子"搜完了也没找到,那就报错
双亲委派的流程图
- 这一套流程,目的是为了约定"优先级"
- 收到一个类名之后,一定是先在 标准库 中找
- 再从 扩展库找
- 最后才在第三方库找
没有为啥,大佬就是这么写的
假设有另一个版本的JVM,就没有这一套"往上传"的过程,直接就从BootstrapClassLoader开始往下找,也是完全可行的
小结(要是没空看上面的就看这个)
JVM内存区域划分
- 程序计数器:是一个小区域,存放着 下一个要执行的指令地址
- 元数据区:类对象(类名是啥,继承了哪个类,实现了哪些接口,有哪些属性,属性都叫什么名字,属性的类型,是private还是public,有哪些方法,方法名是什么,参数列表......)
- 栈:方法之间的调用关系
- 堆 :new出来的对象
- 非静态成员 ,位于堆上(成员存放的位置,是前几年的常见面试题)
- 静态成员 位于元数据区
- 局部变量 处于栈上
类加载
- .class文件 => 内存中的类对象
- 加载 :依据全限定类名,找.class文件,并读取文件内容
- 验证 :校验读到的内容的格式 是否符合要求
- 备准 :分配内存(分配全"0"的,未初始化的内存空间)
- 解析 :针对字符串常量进行初始化
- 初始化 :针对类对象进行填充 ,也会触发父类的加载(前提是父类还没有被加载)
一个进程中,一个类的加载,只会触发一次
类加载触发的时机:
- Java程序一启动,就会加载用到的所有类吗?NO!
- 类加载是一个懒汉模式/懒加载
- Java代码用到哪个类,就会触发哪个类的加载
- 那怎么算用到这个类呢 :
- 构造 这个类的实例
- 调用 /使用 类的静态属性/静态方法
- 使用某个类的时候,如果它的父类还没加载,也会触发父类的加载
面试小tips:
面试中被问到:你是哪一届的,为什么现在来,学校怎么办
- 为什么现在来:我认为一个程序员,学习编程技术,光靠书本是不行的,要尽早的去参加商业级别的项目,在实战中打磨自己的技术能力
- 学校怎么办:我们学校院长也是上述观点,非常支持同学们出去找实习,学校的课只要拿到offer之后,院长签个字,期末回来考试就行了
- (就算你学校院长并没有说过这句话,也要这么说,先拿下offer再说)
太棒了!你居然能耐心地看完这些内容!
说明你的学习能力和自控能力超强呢!
加油加油!我们一起拿下40w年薪!
END✿✿ヽ(°▽°)ノ✿




