|
22 | 22 | - 根据对象的生存周期,将堆分为新生代和老年代,然后根据各个年代的特点采用最适当的收集算法。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用**复制**算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用**标记 - 整理** 或者**标记 - 清除**。
|
23 | 23 | - 严格地说,这并非是一种算法,而是一种思想,或者说是一种复合算法。
|
24 | 24 |
|
| 25 | +Java 的堆内存被分代管理,为什么要分代管理呢?分代管理主要是为了方便垃圾回收,这样做基于2个事实,第一,大部分对象很快就不再使用;第二,还有一部分不会立即无用,但也不会持续很长时间。 |
| 26 | + |
| 27 | + 虚拟机划分为年轻代、老年代、和永久代,如下图所示。 |
| 28 | + |
| 29 | +<imgsrc="http://s0.lgstatic.com/i/image2/M01/8A/BB/CgoB5l14luqAFGZHAABEqvU94zM441.png"alt="img"style="zoom:50%;" /> |
| 30 | + |
| 31 | +- 年轻代主要用来存放新创建的对象,年轻代分为 Eden 区和两个 Survivor 区。大部分对象在 Eden 区中生成。当 Eden 区满时,还存活的对象会在两个 Survivor 区交替保存,达到一定次数的对象会晋升到老年代。 |
| 32 | + |
| 33 | +- 老年代用来存放从年轻代晋升而来的,存活时间较长的对象。 |
| 34 | + |
| 35 | +- 永久代,主要保存类信息等内容,这里的永久代是指对象划分方式,不是专指 1.7 的 PermGen,或者 1.8 之后的 Metaspace。 |
| 36 | + |
| 37 | + |
| 38 | + 根据年轻代与老年代的特点,JVM 提供了不同的垃圾回收算法。垃圾回收算法按类型可以分为引用计数法、复制法和标记清除法。 |
| 39 | + |
| 40 | +- 引用计数法是通过对象被引用的次数来确定对象是否被使用,缺点是无法解决循环引用的问题。 |
| 41 | + |
| 42 | +- 复制算法需要 from 和 to 两块相同大小的内存空间,对象分配时只在 from 块中进行,回收时把存活对象复制到 to 块中,并清空 from 块,然后交换两块的分工,即把 from 块作为 to 块,把 to 块作为 from 块。缺点是内存使用率较低。 |
| 43 | + |
| 44 | +- 标记清除算法分为标记对象和清除不在使用的对象两个阶段,标记清除算法的缺点是会产生内存碎片。 |
| 45 | + |
| 46 | + |
| 47 | + JVM 中提供的年轻代回收算法 Serial、ParNew、Parallel Scavenge 都是复制算法,而 CMS、G1、ZGC 都属于标记清除算法。 |
| 48 | + |
| 49 | +##CMS 算法 |
| 50 | + |
| 51 | +基于分代回收理论,详细介绍几个典型的垃圾回收算法,先来看 CMS 回收算法。CMS 在 JDK1.7 之前可以说是最主流的垃圾回收算法。CMS 使用标记清除算法,优点是并发收集,停顿小。 |
| 52 | + |
| 53 | +CMS 算法如下图所示。 |
| 54 | + |
| 55 | +<imgsrc="http://s0.lgstatic.com/i/image2/M01/8A/DB/CgotOV14luqAB61oAABezTrv-gg961.png"alt="img"style="zoom:50%;" /> |
| 56 | + |
| 57 | +1. 第一个阶段是初始标记,这个阶段会 stop the world,标记的对象只是从 root 集最直接可达的对象; |
| 58 | + |
| 59 | +2. 第二个阶段是并发标记,这时 GC 线程和应用线程并发执行。主要是标记可达的对象; |
| 60 | + |
| 61 | +3. 第三个阶段是重新标记阶段,这个阶段是第二个 stop the world 的阶段,停顿时间比并发标记要小很多,但比初始标记稍长,主要对对象进行重新扫描并标记; |
| 62 | + |
| 63 | +4. 第四个阶段是并发清理阶段,进行并发的垃圾清理; |
| 64 | + |
| 65 | +5. 最后一个阶段是并发重置阶段,为下一次 GC 重置相关数据结构。 |
| 66 | + |
| 67 | +##G1 算法 |
| 68 | + |
| 69 | +G1 在 1.9 版本后成为 JVM 的默认垃圾回收算法,G1 的特点是保持高回收率的同时,减少停顿。 |
| 70 | + |
| 71 | +G1 算法取消了堆中年轻代与老年代的物理划分,但它仍然属于分代收集器。G1 算法将堆划分为若干个区域,称作 Region,如下图中的小方格所示。一部分区域用作年轻代,一部分用作老年代,另外还有一种专门用来存储巨型对象的分区。 |
| 72 | + |
| 73 | +<imgsrc="http://s0.lgstatic.com/i/image2/M01/8A/BB/CgoB5l14luqAR5suAAB5tOFWo20859.png"alt="img"style="zoom:50%;" /> |
| 74 | + |
| 75 | +G1 也和 CMS 一样会遍历全部的对象,然后标记对象引用情况,在清除对象后会对区域进行复制移动整合碎片空间。 |
| 76 | + |
| 77 | + |
| 78 | + |
| 79 | +G1 回收过程如下。 |
| 80 | + |
| 81 | +- G1 的年轻代回收,采用复制算法,并行进行收集,收集过程会 STW。 |
| 82 | + |
| 83 | +- G1 的老年代回收时也同时会对年轻代进行回收。主要分为四个阶段: |
| 84 | + |
| 85 | +- 依然是初始标记阶段完成对根对象的标记,这个过程是STW的; |
| 86 | + |
| 87 | +- 并发标记阶段,这个阶段是和用户线程并行执行的; |
| 88 | +- 最终标记阶段,完成三色标记周期; |
| 89 | +- 复制/清除阶段,这个阶段会优先对可回收空间较大的 Region 进行回收,即 garbage first,这也是 G1 名称的由来。 |
| 90 | + |
| 91 | +G1 采用每次只清理一部分而不是全部的 Region 的增量式清理,由此来保证每次 GC 停顿时间不会过长。 |
| 92 | + |
| 93 | +总结如下,G1 是逻辑分代不是物理划分,需要知道回收的过程和停顿的阶段。此外还需要知道,G1 算法允许通过 JVM 参数设置 Region 的大小,范围是 1~32MB,可以设置期望的最大 GC 停顿时间等。有兴趣读者也可以对 CMS 和 G1 使用的三色标记算法做简单了解。 |
| 94 | + |
| 95 | +##考察点 |
| 96 | + |
| 97 | +总结 JVM 相关的面试考察点如下: |
| 98 | + |
| 99 | +1. 深入了解 JVM 的内存模型和 Java 的内存模型; |
| 100 | + |
| 101 | +2. 要了解类的加载过程,了解双亲委派机制; |
| 102 | + |
| 103 | +3. 要理解内存的可见性与 Java 内存模型对原子性、可见性、有序性的保证机制; |
| 104 | + |
| 105 | +4. 要了解常用的 GC 算法的特点、执行过程,和适用场景,例如 G1 适合对最大延迟有要求的场合,ZGC 适用于 64 位系统的大内存服务中; |
| 106 | + |
| 107 | +5. 要了解常用的 JVM 参数,明白对不同参数的调整会有怎样的影响,适用什么样的场景,例如垃圾回收的并发数、偏向锁设置等。 |
| 108 | + |
| 109 | +附录:JVM 相关的面试真题 |
| 110 | + |
| 111 | +<imgsrc="http://s0.lgstatic.com/i/image2/M01/8A/DB/CgotOV14luqALq0IAACqj_5S_nw868.png"alt="img"style="zoom:50%;" /> |
| 112 | + |
| 113 | +<imgsrc="http://s0.lgstatic.com/i/image2/M01/8A/BB/CgoB5l14luuAJqWRAACFJ6QM8Tg783.png"alt="img"style="zoom:50%;" /> |