JVM
一、JVM
1.0 简介
(1) 简介
Java虚拟机 主要是面试题导向 jvm诞生的初心就是为了让你不用理解底层
《深入理解Java虚拟机》一书问世,面试官带火了 ,断章取义拿出来作为面试题
这本书治疗失眠效果非常好
重点理解:jvm内存区域划分 类加载机制 垃圾回收机制
2.0 内存区域
(1) 简介
为什么要进行划分区域?
JVM Java虚拟机:仿照真实的机器 真实的操作系统进行设计
真实的操作系统中,对于进程的空间地址进行了分区域的设计 JVM仿造了这样的设计8

划分不同区域之后 就有不同的分工
(2)四个核心区域
JVM具体是怎么划分的呢?
核心区域四个:
程序计数器(内存空间) 很小的区域 只是用来记录当前指令执行到哪个地址了
元数据区: 我们Java写的代码 经过Javac编译成.class文(二进制数据) class文件要加载到内存中(类加载) 而元数据区就是保存当前类被加载好的数据(类对象)~
栈 保存方法的调用关系 每次调用方法 就会进入到方法内部执行 当方法执行完毕 返回到调用位置 继续往后面走
其实理解上来说 也是数据结构那里栈的先进后出 属于是数据结构栈的应用
虚拟机 就是虚拟一套世界~ 栈这个空间 一般来说就是几MB几十MB这样的情况(也可以更改)
堆保存new对象的
//这里的栈和堆不是数据结构里面的栈和堆

堆是JVM中最大的空间区域了
如果堆上的对象 不再使用了的话 就需要被释放掉(垃圾回收)
图片辅助理解:

元数据区和堆 整个Java进程共用一份 程序计数器和栈 一个进程可能有很多份(每个线程有一份)
3.0 类加载
(1) 类加载过程
站在面试角度 类加载 主要关心两个方面
类加载的步骤有哪些
三个大的阶段 第二个阶段又分成了三个步骤
a) 加载
找到.class文件 根据类的全限定名(包名+类名 形如java.lang.String)打开文件 读取文件内容并存到内存里~
b)验证:解析,校验.class文件读到的内容是否是合法的 并把这里的内容转成 结构化的数据(.class文件 二进制文件 格式是有明确要求的)
//Java官方文档:Java SE 文档 --- API 和文档 | Oracle 中国

c)准备
给类对象申请内存空间 此处申请的内存空间 相当于是"全0"的空间
d)解析:针对字符串常量 进行初始化
字符串常量 本身就包含在.class文件中 就需要.class文件里解析出来的字符串常量 放到内存空间里(元数据区 常量池中)
e)初始化
针对刚才所谈到类对象 针对类对象的各种属性进行填充 包括类中的静态成员
如果这个类还有父类 并且父类还没有加载呢 此环节也会触发父类的类加载
(背下来)
类加载中的"双亲委派模型"
//这个东西本身不是一个很紧要的机制 但是之所以成为了一个知名面试题 最主要的原因是起了一个好名字 ~ 哈哈哈 感觉很高大上 自动装箱/自动拆箱 Java内存模型 spirng里面的IOC AOP 代理模式
//补充
类加载的时机 : 可以理解为懒汉模式时机 懒汉加载 "用到的时候再加载"
构造这个类的实例 调用/使用 静态属性/静态方法 使用某个类的时候,如果他的父类还没有加载 也会触发父类的加载
(2) 双亲委派模型
双亲委派模型 描述了类加载中 根据全限定类名 找到.class文件的过程
//全限定类名:用于唯一标识一个类的完整名称
更准确的来说 单亲委派模型/父亲委派模型
* 类加载器
是一个代码块 这个代码块的功能专门就是用来进行类加载的
这个可以自定义的 可以自定义好之后 放到双亲委派模型

*过程
从appliacation入口 如果第三库里面没有 就上一级到extension里面找 以此类推
这里的爷父孙不是继承关系 是一种引用指向关系
可以按照下面的这个来进行类比

Boot可以理解为大老板 Extension可以理解为中层管理人员 Application可以理解为基层员工
4.0 垃圾回收机制(GC)
(1)引入
之前学习C语言的时候 我们了解到C语言需要申请内存malloc 申请之后一定要手动调用free释放内存空间 这样的机制 程序员在写代码的时候 总是容易忘记手动释放内存
而Java语言吸取了C的示例 jvm有自动垃圾回收机制 之后的各种编程语言也沿用了这种方式
但是自动垃圾回收机制也是有代价的: 效率下降 有的时候还是"卡了"STW问题
c++为了解决内存泄漏问题 引入了智能指针机制
rust的解决办法:通过严格的编译器检查 来对代码中的内存问题进行叫校验 编译的过程中做检查发现问题了直接报错 但是这么做也是有代价的 就是语法变得非常复杂 GC依然是主流机制
(2) 工作过程
简单来说就是找到垃圾 释放垃圾
(3) 如何找到垃圾
引用计数 (Python PHP采取的方案)

同样 这么做也是有代价的
内存消耗的更多:需要额外分配空间来记录引用记数 如果引用计数分配的空间是4个字节 newTest8个字节 这样相当于提高了50%的空间占用率
可能造成循环引用:

相当于死锁的那个情况 此时 ab这两个对象的引用不为0 虽然不为0 但是这两对象都无法使用
可达性分析(Java采取的方案)
上面的引用计数是空间换时间 这里的可达性分析是时间换空间
可达性分析的过程是:以特定对象(栈 局部变量 常量 静态成员)为起点 尽可能地遍历 判定某个对象是否能访问到
每次访问一个对象 都会把这个对象标记成"可达" 当完成所有对象的遍历之后 未被标记成"可达"的对象就是垃圾 之后回收掉
这个过程是周期性的 每隔一段时间就触发一次
下面的图片表示这个过程:

代价:每次触发 都要遍历一遍对象 如果对象很多的情况 这样以此遍历的成本也是非常大的
(4)如何释放垃圾?
释放垃圾的机制:标记-清除 复制算法 标记-整理
Java给出的答卷:分代回收 把上面的机制结合起来
标记-清除:
把垃圾对象得内存 直接释放掉 这样会产生内存碎片问题

虽然存在一定的空闲空间 但是这些空闲时间不是连续的
后面我们申请内存的时候 有麻烦 因为申请内存的时候 是申请一段连续的内存空间
如果内存碎片非常多 就会导致你总的空闲时间虽然很大 但是你但凡想申请一个稍微大一点的内存
都是失败 总4G 出现上述的情况 申请1G空间都可能失败
复制算法:
一次只是使用空间的一半
把不是垃圾的对象 拷贝到另外一侧 然后再把这一侧给整体的释放掉

解决了内存碎片问题
但是也有缺点:
一旦不是垃圾的对象比较多 内存搬运数据很多的话 复制的成本也会还大
还有就是内存直接砍半 2G的内存立马变成1G(内存的空间利用率低)
标记-整理:
结合了上面两个方法 既能解决内存碎片 也能保证内存利用率

删除了那些垃圾之后 把其他元素搬运到原来消除的位置
缺点:内存搬运数据的操作开销是挺大的 一旦对象很多很大 复制成本很大
分代回收:
Java给出的答卷 分代回收 把上面123种方法结合起来(主要是2 3 )
代: 对象的年龄 GC抡次 (GC轮次越多 表示越老) 某个对象经历了一轮GC可达性分析之后 不是垃圾 此时对象的年龄就+1

针对不同年龄采取不同的策略
如果某个对象 已经是年龄比较大的了 此时大概率还会继续存在很久~
通俗理解就是"要死早死了" 留下来的都是老油条 不容易死去
其实反应在生活中 就以面试为例 一个公司只有50个岗位 但是有1000份简历 公司不可能一个一个都面试笔试完毕(成本很大) 所以就先初步筛选 第一轮过滤到一大部分 然后再次细分之后
开始多轮面试 过了好几轮还在的 最后留任 公司的某位淘汰也是这个道理但是时间周期是比较长的
新创建的对象就放到"伊甸区" 绝大部分的伊甸区 都活不过第一轮GC
伊甸区=>幸存区:复制算法
幸存区中的对象:也是要经历GC的扫描 每一轮GC都会消灭一大部分对象 剩余的对象再次通过复制算法 复制到另外一个幸存区

如果这个对象在幸存区中经历了多次复制 都存活下来了 对象的年龄的就大了 就会晋升到老年代
以上就是JavaEE初阶的全部内容 好事多磨~