面试害怕考到JVM? 看这一篇就够了~ 不念不忘少年蓝@ 2024-03-31 15:29 6阅读 0赞 **目录** 前言 一、JVM内存划分 二、类加载 2.1、类加载是在干什么? 2.2、类加载的过程 2.3、何时触发类加载? 2.4、双亲委派模型(重点考察) 2.4.1、什么是双亲委派模型? 2.4.2、涉及到的类加载器 2.4.3、详细过程图解 三、GC(垃圾回收机制) 3.1、STW问题(Stop The World) 3.2、GC回收哪部分内存? 3.3、垃圾对象的判定算法 3.3.1、引用计数法(非JVM采取的办法) 3.3.2、可达性分析(JVM采取的办法) 3.4、垃圾回收算法 3.4.1、标记-清除算法 3.4.2、复制算法 3.4.3、标记整理算法 3.4.4、分代算法 -------------------- ## 前言 ## 面试中要考到有关JVM的话题,主要也就是三个方面:1.JVM内存划分,2.JVM类加载,3.JVM的垃圾回收;弄清楚这三个方面的内容,带你体会面试就如聊天?本篇会用通俗简洁的话,高效的带你理解JVM这三个模块! -------------------- ## 一、JVM内存划分 ## java程序,是一个名字为java的进程,这个进程就是所说的“JVM”; JVM 会从操作系统中申请一块大的内存空间,在此基础上分成几个小的区域; > 区域划分如下图: ![fa327260032144bfae377e0d03318b7b.png][] **注意:上图中每一个虚线框都对应一个线程;程序计数器是每个线程都有一个;** > 这些区域分别存放什么?(面试如果问到,如下回答即可) 1.堆:存放new出来的对象;(成员变量) 2.方法区:存放的是类对象;(静态变量) 3.栈(虚拟机栈, 本地方法栈):存放方法之间的调用关系;(局部变量) 4. 程序计数器:存放的是下一个要执行的指令; **注意:变量存放在哪一个区域,和变量类型无关!和变量的形态(局部,成员,静态)有关!** > 测试:以下变量存放在什么位置?(笔试题中会出现) ![209e065accdc4baf9f4600b56f0d91b6.png][] **解释:** 变量a是成员变量,所以存放在堆里 变量b是成员变量,所以存放在堆里 变量c是静态变量,在类对象里,所以存放在方法区中; testHead这个引用是静态的,在方法区中,他new出的对象是在堆里的; test这个引用是局部变量,所以就在栈上,他new出的对象在堆中; -------------------- ### 二、类加载 ### ### 2.1、类加载是在干什么? ### java程序在运行之前,会先编译,也就是由 .java 编译成 .class文件(二进制字节码文件),运行的时候,java进程(JVM)就会读取到对应的 .class 文件,并解析内容,在内存中构造出类对象进行初始化; 简而言之就是: 类 从 文件 加载到内存中; ### 2.2、类加载的过程 ### > 下图为类的生命周期: ![ce910be3a55349c7b74a585ef89382e7.png][] **解释:** 前 5 步是固定的顺序并且也是类加载的过程,其中中间的 3 步我们都属于连接,所以对于类加载来 说总共分为以下五个步骤: 1. 加载; 2. 连接 =>(1.验证、2.准备、3.解析); 3. 初始化; > 对类加载步骤的解释: 1. 加载:找到 .class 文件,读取文件内容,按照 .class 规范的格式来解析; 2. 验证:检查当前的 .class 里的内容格式是否符合要求; 3. 准备:给类里的静态变量分配内存空间; *例如,static int a = 6; 这段代码在准备阶段就会给a分配4个字节的内存空间,同时这些空间的初始值都是0;* 4. 解析:初始化字符串常量,把**符号引用(占位符)**替换成**直接引用(内存地址)**; *例如,String str = "hello!"; 类加载之前,"hello!"这个字符串常量并没有分配内存空间,因此 str 里就无法保存字符串常量的真实地址,只能使用一个占位符标记一下(标记了这里是"hello!"这个字符串常量的内存地址),等到真正给"hello!"分配了内存后(类加载完后),就可以用真正的地址代替之前的占位符;* 5.初始化:针对类进行初始化,初始化顺序如下 *父类(静态变量、静态代码块)–>子类(静态变量、静态代码块)–>父类(变量、代码块)–> 父类构造器–>子类(变量、初始化块)–>子类构造器。* ***注意:静态代码和静态变量同级,变量和代码块同级。谁在前先执行谁。类只会初始化一次。*** ### 2.3、何时触发类加载? ### 这里并不是程序一启动就加载了,而是类似于\[ 懒汉模式 \] ,使用到这个类的时候,才会触发加载; > 怎么算才是使用到这个类? 1. 创建了这个类的实例; 2. 使用了类的静态方法/静态属性; 3.实用类的子类(加载子类会触发加载父类); ### 2.4、双亲委派模型(重点考察) ### #### 2.4.1、什么是双亲委派模型? #### 一个类加载器收到类加载请求,首先自己不会加载这个类,而是把这个请求委派给父类加载器完成,每一层都是如此,因此所有加载请求最终都会送到最顶层的加载器中,只有父加载器反馈无法加载这个请求,子类才会尝试去加载; #### 2.4.2、涉及到的类加载器 #### 1. Bootstrap ClassLoader :负责加载标准库中的类; 2. Extension ClassLoader:负责加载JVM扩展的库的类(标准库中没有,但JVM自己实现出了); 3. Application ClassLoader :负责加载我们自己的项目中的自定义类; #### 2.4.3、详细过程图解 #### ![178c3a3d1d314e78a8c08c20dc02420e.png][] 有意思的是,上述过程并未涉及到 “双亲”,只是 “单亲”,这里的 “双亲” 实际上是机翻出来的,更直白其实可以叫 “单亲委派模型”... -------------------- ## 三、GC(垃圾回收机制) ## 在学习C语言的过程中,需要通过 malloc 申请内存,最后通过 free 进行释放,这里就容易存在一个问题——忘记free,造成内存泄漏;而GC(垃圾回收)就是一个主流处理方案; > GC是干什么的呢? 就是一个自动释放内存的机制;我们只需要负责申请内存,释放内存的工作交给JVM完成,JVM会自动判定当前内存是否不再使用,若不再使用,就自动释放;(类似开车 => 手动挡 升级 自动档) ### 3.1、STW问题(Stop The World) ### C++为何不引入GC?因为GC存在一个最大的问题就是会引入额外的 “空间+时间” 开销; 空间上:消耗额外的CPU / 内存资源; 时间上:最大的问题——STW问题(Stop The World); > 什么是STW问题? 当程序运行到需要GC释放内存的时候,就有需要消耗一定的时间,反应到用户这里,就有可能存在明显的卡顿; 那那那那...为什么我们还要用他?因为在实际的开发中,开发效率是大于运行效率的~ ### 3.2、GC回收哪部分内存? ### 方法区?类对象加载一次之后便不会卸载;栈?释放时机确定,不用回收;程序计数器?固定内存空间,不必回收;**GC主要就是针对堆来回收的;** > 如下图: ![41a85639fa7d4a09a3df101d39093df5.png][] ### 3.3、垃圾对象的判定算法 ### > 如何判断一个某个对象是否是垃圾? 如果一个对象没有任何引用能够指向他,这个对象就是视为垃圾了; #### 3.3.1、引用计数法(非JVM采取的办法) #### *注意:此方法不是JVM采取的方法,Python、PHP使用这个方法;* 具体的,给每个对象加上一个计数器,这个计数器就表示 “当前的对象有几个引用”; > 如下图: ![cdbb863451014d6597a067865421ccef.png][] > 解释: 每多一个引用指向该对象,计数器就+1; 每少一个引用指向该对象,计数器就-1; 当计数器的数值为0时,就说明这个对象已经没有人能够再使用了,此时就可以进行释放; > 存在缺点: 1. 空间利用率低,尤其是小对象(例如:计数器本身大小为int,对象里也只有一个int大小的成员,相当于空间增加了一倍); 2. 可能出现循环引用的情况,如下: ![8298b8adb30240c49221963be7d65212.png][] > 解释: 当前这两个对象引用计数器为1,因为即使t1, t2两个引用被置为空,但实际上时两个对象在相互引用,此时外界代码是无法访问的,但由于引用计数器不是0,所以无法进行释放; #### 3.3.2、可达性分析(JVM采取的办法) #### 约定一些特定的变量,成为 “GC roots”, 每隔一段时间,从GCroots出发,进行遍历,查询哪些变量是能够被访问到的,能被访问到的变量就称为 “可达”,否则就是 “不可达”; > GC roots对象可以是以下几种: \-- 栈上的变量; \-- 常量池引用的对象; \-- 方法区中静态属性引用对象; \-- 方法区中常量引用的对象; > 具体的如下图: ![72225406da144ba3829dee49e8865e9c.png][] > 解释: 上图中,通过 GC roots就可以找到object1、object2、object3、object4;而object5、6、7则不可以被访问到,所以5、6、7就是垃圾了; ### 3.4、垃圾回收算法 ### #### 3.4.1、标记-清除算法 #### 简单来说,就是标记出垃圾后,直接把对象对应的内存空间进行释放; > 如下图: ![4a9686209145410eb1ab3fb2ee4815af.png][] > 解释: 上图中,黑色部分就是被标记要清除的部分,经过回收后,就剩下了存活对象; > 缺点: 1.效率:标记和清除这两个过程的效率都不高; 2.空间(内存碎片):如上图,标记就清除后会产生大量不连续的内存碎片,碎片太多可能会导致之后程序运行中需要分配较大对象时,可能无法找到连续且足够大的空间而无法申请空间; #### 3.4.2、复制算法 #### 这是针对内存碎片问题,引入的办法; 具体的,将内存分成两块大小相等的空间,但只使用其中的一块,当需要进行垃圾回收时,就把正在使用的那块空间上还存活的对象复制到另一块上,再将使用过的那块内存全部清空;这样做的好处就是不用在考虑内存碎片问题; > 如下图: ![cf2c32b03de44c209328039a9fd75bdc.png][] > 缺点: 1. 空间利用率相比标记清除法更低了; 2. 若一轮GC下来,大部分需要保留,只有极少数要回收,这时候复制的开销就很大了; #### 3.4.3、标记整理算法 #### 标记过程与 “标记-清除算法”一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端连续性的覆盖,然后直接清除掉边界以外的空间;(类似于顺序表的删除元素) > 如下图: ![fc517576a51c436685bd3538924d40bb.png][] > 评价: 相对于复制算法而言,空间利用率提升了,同时也解决了内存碎片化问题,但是搬运操作比较耗时; #### 3.4.4、分代算法 #### 分代算法总和了上面所说的三种算法,通过区域划分,实现不用区域用不同的垃圾回收策略,从而实现更好的垃圾回收;也就是我们常说的“因地制宜”~ > 如下图: ![8ca649946e644ba2a145f6c7719f1f0a.png][] > 解释: 1. 刚创建出来的对象,进入伊甸区; 2. 若新对象熬过一轮GC,没挂,就通过**复制算法**,复制到生存区; 3. 生存区的对象也要经历GC,每熬过一次GC,就会通过**复制算法**拷贝到另一个生存区(只要这个对象不死亡,就会在两个生存区来回拷贝); 4. 如果一个对象在生存区中,反复坚持了很多轮还没去世,就会被放到老年代(老年代GC的频率会降低); 5. 若对象来到了老年代,也会进行定期的GC,只是频率更低了;老年代采取**标记整理**的方式来处理垃圾; **特殊处理:若新创建的对象非常大,则直接进入老年代;因为一个大的对象进行复制算法,开销太大;另一个角度考虑,既然是一个很大的对象,费这么大开销创建出来,肯定不是立即就销毁的;** -------------------- ![5df663ed512442caba6b6702514b7382.gif][] [fa327260032144bfae377e0d03318b7b.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/a60a90f287ff4f95a80838bac23ccfab.png [209e065accdc4baf9f4600b56f0d91b6.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/728f26b0516d4023a3aa23e79239b51b.png [ce910be3a55349c7b74a585ef89382e7.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/cf4893f0adb8471b8c79fa8871ef740e.png [178c3a3d1d314e78a8c08c20dc02420e.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/18e5a94583554e1dba3fcb9520bff7a7.png [41a85639fa7d4a09a3df101d39093df5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/87ff75ae0acd482fb39d8dfde1de9466.png [cdbb863451014d6597a067865421ccef.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/179abff167494af4bfd9e0776d57a6f7.png [8298b8adb30240c49221963be7d65212.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/2a919ee00d2342beb20b613c4f869564.png [72225406da144ba3829dee49e8865e9c.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/a44213dc460c4dfd91dbf01beab9b590.png [4a9686209145410eb1ab3fb2ee4815af.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/0fe7ee354a704748a87436b9863afa17.png [cf2c32b03de44c209328039a9fd75bdc.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/f3f85d80edca4ea7a4e373bf9ed807ca.png [fc517576a51c436685bd3538924d40bb.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/1297de1910684c51b460f27c7950409d.png [8ca649946e644ba2a145f6c7719f1f0a.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/969d29978ab34a85870c2e241ec0e76c.png [5df663ed512442caba6b6702514b7382.gif]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/31/a2b1e4de0efc4905a60a701c58a3c2e3.gif
还没有评论,来说两句吧...