🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] <br/> > ### 判断对象是否已死亡 * **引用计数法**,引用计数法无法解决循环引用的问题。 * **根搜索算法**,通过一系列的称为 `GC Roots`的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。如下可作为`Root`: * java虚拟机栈中引用的对象 * 本地方法栈中引用的对象 * 方法区的静态属性引用的对象 * 方法区的常量引用的对象 <br/> > ### 四种引用类型 * **强引用** * **软引用**,当系统内存不够时才回收软引用的对象。举例,博客的列表缓存。 * 通过软引用对博客文章进行缓存 * **弱引用**,当垃圾回收器发现某块内存上只有弱引用(没有强引用)时,不管当前内存是否足够都会回收这块内存。 * 优惠券`coupanList`和用户`userList`的对应关系,通过`WeakHashMap<Coupan, <List<>WeakReference<User>>>`类型的`weakCoupanHM`来存储其对应关系。 * **虚引用** <br/> > ### 垃圾收集算法 * **标记-清除算法**,会产生大量不连续的内存碎片。一般是老年代收集算法`cms` * **复制算法**,将内存按大小分为两块,当其中一块用完将存活对象复制到另一块,并将前一块进行回收。一般是新生代收集算法,新生代对象回收频繁且大部分对象都会被回收。`Eden`区和两块`Survivor`区,大小比为`8 : 1 : 1`,当复制算法收集时`Survivor`区大小不够时,需要依赖老年代进行分配担保。 * **标记-整理算法**,在标记清除的基础上对所有存活对象向内存一侧移动,再进行回收,避免了内存碎片的问题。 <br/> > ### 安全点 * 安全点意味着在这个点时,所有工作线程的状态是确定的,JVM 就可以安全地执行 GC 。 * 一般会在如下几个位置选择安全点:循环的末尾,方法临返回前,调用方法之后,抛异常的位置。 * **主动式中断**:在 GC 发生时,不直接操作线程中断,而是简单地设置一个标志,让各个线程执行时主动轮询这个标志(标志位置与安全点重合),发现中断标志为真时就自己中断挂起,JVM采用主动式中断。**抢断式中断**:在 GC 发生时,首先中断所有线程,如果发现线程未执行到 Safe Point,就恢复线程让其运行到 Safe Point 上。 <br/> > ### 并发`concurrent` 和 并行`paralla` ![](https://i.loli.net/2019/03/12/5c87344570bc8.png) * **并发**`concurrent` 是两个队列交替使用一台咖啡机 * **并行**`paralla` 并行是两个队列同时使用两台咖啡机 * 并发`concurrent` 和 并行`paralla`都可以是多个线程,就看这些线程能不能同时间被(多个)CPU执行,如果可以就说明是并行,而并发是指多个线程被(一个)CPU轮流切换执行。 <br/> > ### 垃圾收集器 ![](https://i.loli.net/2019/03/12/5c872c1e1ee32.png) * **新生代收集器**: `Serial` 、`ParNew`、 `Parallel Scavenge` * **老年代收集器**: `Serial Old` 、`Parallel Old`、 `CMS` * 堆内存垃圾收集器:`G1` * `JVM`默认收集器,新生代`Parallel Scavende`,老年代`Serial Old` ![](https://i.loli.net/2019/03/12/5c872cfbd5c4a.png) > ### `Serial` 收集器 * `Serial` 是一款用于新生代的单线程收集器,采用复制算法进行垃圾收集。`Serial `进行垃圾收集时,只用一条线程执行垃圾收集工作,它在收集的同时,所有的用户线程必须暂停(`Stop The World`)。 * **优点**:简单而高效(与其他收集器的单线程比),对于限定单个`CPU`的环境来说,Serial收集器减少了线程交互的开销。 所以运行在`Client`模式下的虚拟机来说是一个不错的选择 ![](https://i.loli.net/2019/03/12/5c872dee87f9e.png) > ### `ParNew` 收集器 * `ParNew` 就是一个 `Serial` 的多线程版本,针对新生代,其它与Serial并无区别。`ParNew` 在单核 CPU 环境并不会比 `Serial` 收集器达到更好的效果,它默认开启的收集线程数和 `CPU` 数量一致,可以通过 `-XX:ParallelGCThreads` 来设置垃圾收集的线程数。 * **适用场景**:多核服务器;与 `CMS` 收集器搭配使用。当使用` -XX:+UserConcMarkSweepGC `来选择 CMS 作为老年代收集器时,新生代收集器默认就是 `ParNew`,也可以用 `-XX:+UseParNewGC `来指定使用 ParNew 作为新生代收集器。 ![](https://i.loli.net/2019/03/12/5c872eb35c508.png) > ### `Parallel Scavenge` 收集器 * `Parallel Scavenge` 也是一款用于新生代的多线程收集器,与 `ParNew` 的不同之处是`ParNew `的目标是尽可能缩短垃圾收集时用户线程的停顿时间,`Parallel Scavenge `的目标是达到一个可控制的吞吐量。吞吐量就是 CPU 执行用户线程的的时间与 CPU 执行总时间的比值。 * **适用场景**:注重吞吐量,高效利用 CPU,需要高效运算且不需要太多交互。可以通过` -XX:MaxGCPauseMillis` 来设置收集器尽可能在多长时间内完成内存回收,可以通过` -XX:GCTimeRatio` 来精确控制吞吐量。 ![](https://i.loli.net/2019/03/12/5c872fa7018d4.png) > ### `Serial Old` 收集器 * `Serial Old` 收集器是 `Serial` 的老年代版本,同样是一个单线程收集器,采用标记-整理算法。 * **适用场景**:Client 模式(桌面应用);单核服务器;与 Parallel Scavenge 收集器搭配;作为 CMS 收集器的后备预案。 ![](https://i.loli.net/2019/03/12/5c873012cabb0.png) > ### `Parallel Old` 收集器 * `Parallel Old` 收集器是 `Parallel Scavenge `的老年代版本,是一个多线程收集器,采用标记-整理算法。可以与 `Parallel Scavenge` 收集器搭配,可以充分利用多核 CPU 的计算能力。 * **适用场景**:与`Parallel Scavenge` 收集器搭配使用;注重吞吐量。jdk7、jdk8 默认使用该收集器作为老年代收集器,使用` -XX:+UseParallelOldGC `来指定使用 `Paralle Old `收集器。 ![](https://i.loli.net/2019/03/12/5c8731a1d9b8f.png) > ### `CMS`收集器 * `CMS` 收集器是一种以最短回收停顿时间为目标的收集器,以 “ 最短用户线程停顿时间 ” 著称。优点:并发收集、低停顿,缺点:标记-清除算法导致的空间碎片,无法处理浮动垃圾(并发清除时,用户程序仍在运行所产生的垃圾) * 整个垃圾收集过程分为 4 个步骤: * ① 初始标记:标记一下 GC Roots 能直接关联到的对象,速度较快。`stop the world` * ② 并发标记:进行 GC Roots Tracing,标记出全部的垃圾对象,耗时较长。 * ③ 重新标记:修正并发标记阶段引用户程序继续运行而导致变化的对象的标记记录,耗时较短。`stop the world` * ④ 并发清除:用标记-清除算法清除垃圾对象,耗时较长。 * 整个过程耗时最长的并发标记和并发清除都是和用户线程一起工作,所以从总体上来说,CMS 收集器垃圾收集可以看做是和用户线程并发执行的。 ![](https://i.loli.net/2019/03/12/5c8730c5660ed.png) > ### `G1`收集器 * `G1` 收集器是 jdk1.7 才正式引用的商用收集器,现在已经成为 jdk9 默认的收集器。前面几款收集器收集的范围都是新生代或者老年代,`G1` 进行垃圾收集的范围是整个堆内存,它采用 “ 化整为零 ” 的思路,把整个堆内存划分为多个大小相等的独立区域(`Region`),在 G1 收集器中还保留着新生代和老年代的概念,它们分别都是一部分 `Region`,如下图: ![![](https://i.loli.net/2019/03/12/5c8731eca68df.png)](https://i.loli.net/2019/03/12/5c8747986ce56.png) * `G1` 收集器可以 “ **建立可预测的停顿时间模型** ”,`G1` 采用**增量回收**的方式,每次回收一些区块,而不是整堆回收。它维护了一个列表用于记录每个` Region` 回收的价值大小(回收后获得的空间大小以及回收所需时间的经验值),这样可以保证` G1 `收集器在有限的时间内可以获得最大的回收效率。 ![](https://i.loli.net/2019/03/12/5c87331a77d71.png) * G1 收集器收集器收集过程有初始标记、并发标记、最终标记、筛选回收,和 CMS 收集器前几步的收集过程很相似: ① 初始标记:标记出 `GC Roots` 直接关联的对象,这个阶段速度较快,需要停止用户线程,单线程执行。 ② 并发标记:从 `GC Root `开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。 ③ 最终标记:修正在并发标记阶段引用户程序执行而产生变动的标记记录。 ④ 筛选回收:筛选回收阶段会对各个` Region` 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划(用最少的时间来回收包含垃圾最多的区域,这就是 `Garbage First `的由来——第一时间清理垃圾最多的区块),这里为了提高回收效率,并没有采用和用户线程并发执行的方式,而是停顿用户线程。 * **适用场景**:要求尽可能可控 `GC `停顿时间;内存占用较大的应用。可以用`-XX:+UseG1GC`使用` G1 `收集器,jdk9 默认使用` G1` 收集器。 <br/> > ### `JVM`分代收集 * 对象优先在`Eden`分配。当`Eden`区没有足够的空间进行分配时,虚拟机就会发动一次新生代的垃圾回收动作(`Minor GC`)。 * 大对象直接进入老年代,大对象指大量连续内存空间的Java对象,比如那种很长的字符串以及数组。`JVM`中提供了`-XX:PretenureSizeThreshold`,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免`Eden`区及两个`Survivor`区之前发生大量的内存复制。 * 虚拟机会给每一个对象定义了一个对象年龄(`Age`)计数器。如果对象在Eden出生并经过第一次`Minor GC`后仍然存活,并且能被`Survivor`容纳的话,将其移动到`Survivor`空间中,并且对象年龄设为1,每经历一次`Minor GC`年龄加1,默认达到15岁进入老年代,可以通过参数`-XX:MaxTenuringThreshold`设置年龄阈值。(如果在`Survivor`空间中相同年龄所有对象大小的总和大于`Survivor`空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无序等到`MaxTeuringThreshold`所要求的年龄。) <br/> <br/> *** 参考: [7种JVM垃圾收集器特点,优劣势、及使用场景](https://www.zhihu.com/search?type=content&q=%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8) [并发与并行的区别](https://www.zhihu.com/question/33515481/answer/199929767) [G1 垃圾收集器介绍](https://www.javadoop.com/post/g1) [Java虚拟机系列之垃圾回收机制(1)](https://juejin.im/post/5b2fbea76fb9a00e5d798af3) [Java虚拟机系列之垃圾回收机制(2)](https://juejin.im/post/5b30ea636fb9a00e3b7fb1f9)