初涉JVM——2垃圾回收
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
对象优先在Eden区分配
目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.下面我们来进行实际测试以下。
在测试之前我们先来看看 Minor GC 和 Full GC 有什么不同呢?
- 新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
- 老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
**为什么要这样呢?**为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
长期存活的对象进入老年代
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
来设置。
垃圾回收我们首先要搞清楚一个问题
哪些内存需要回收?
当然是回收已经没用的对象,那怎么判断一个对象是不是还有用呢?判断一个对象是否需要回收主要有这两种回收算法:引用计数&可达性分析
垃圾回收判断算法
(这里两个判断算法就简介一下)
引用计数
当一个地方引用它时,就+1;当引用失效时,就-1;GC会回收那些计数为0的对象。
优点:简单,快捷
缺点:对于相互引用的对象会失效
可达性分析
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
那哪些对象可以作为GCRoot呢?有这些我们可以看看:
1、虚拟机栈(局部变量区,详情见JVM1)中引用的对象
2、方法区中类静态引用的对象
3、方法区中常量引用的对象
4、本地方法栈中JNI(native方法)引用的对象
5、所有被同步锁(synchronized关键字) 持有的对象
finalize()方法判断对象死活
在可达性分析算法中,标记为不可达对象并非“死亡”,在回收之前会至少经历两次标记引用过程
这里的两次标记通过finalize()方法来操作的,具体为
第一次标记,并进行一次筛选
筛选条件:该对象是否有必要执行finalize()方法
当对象没有覆盖finalize方法,或者finalize方法已经被虚拟机调用过,虚拟机将以上两种情况视为“没有必要执行”,对象被回收
第二次标记
如果对象被判定为有必要执行finalize方法,那么这个对象将会被放置在一个名为F-queue的队列中,由一条虚拟机自动建立的、低优先级的finalize线程执行
执行:虚拟机触发这个方法,但不会承诺会等待运行结束
原因:如果一个对象的finalize方法中执行缓慢,或者发生死循环,可能会导致F-queue队列中的其他对象永久处于等待状态,甚至内存回收系统崩溃
finalize方法
1、稍后GC会对F-queue中的对象进行第二次小规模标记,
2、如果在重新与引用链上的一个对象建立关联,那么在第二次标记时会将它移出“即将回收”的集合
3、如果对象没有再次被引用,那么就会被回收
四大引用 延伸
1.强引用(StrongReference)
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
2.软引用(SoftReference)
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
3.弱引用(WeakReference)
如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4.虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
回收策略
标记-清除算法
很简单,就是先标记,后清除。先标记首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
- 效率问题。如果 Java 堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
- 空间问题(标记清除后会产生大量不连续的碎片)
标记-整理算法
简单来说就是让所有的存活对象移动到同一端,然后清理掉端边界以外的内存。
复制算法
赋值算法是将内存容量划分为大小相同的两块,每次都只用一块,当一块用完了,就将存活的对象复制到另一块,再清理掉已使用的内存空间。(我们常见的from-to)区就是采用复制算法。这个算法显然有一个缺点,就是太浪费内存,不管怎么回收,永远有一半是不能利用的。
分代收集算法
这个算法就是根据各个年代的特点采用最合适的收集算法,新生代采用复制算法,老年代采用标记-清除算法或者标记整算法。
其实针对新生代和老年代的垃圾收集分为几种不同的类型,如我们常说的
新生代收集 (Minor GC / Young GC):指目标只是新生代的垃圾收集。
老年代收集 (Major GC / Old GC):指目标只是老年代的垃圾收集,目前只有CMS收集器的并发收集阶段是单独收集老年代的行为。
这两种都是属于部分收集(Partial GC),而另一种是整堆收集 (Full GC):收集整个Java堆和方法区的垃圾收集。
我们经常把老年代收集(Major GC )和整堆收集 (Full GC)混为一谈,因为MajoyGC比较少见,大多数情况下我们说的收集只要是包含了老年代的收集,其实就是FullGC了。
这里放几张关于新生代中的分配与回收和长期存活对象晋升老年代的动图,我觉得很好理解。
新生代中对象的分配与回收
第一次GC
对象晋升老年代
垃圾收集器
Serial收集器
(新生代采用复制算法,老年代采用标记-整理算法)
这是最基本、历史最悠久的单线程收集器。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
ParNew收集器
( 新生代采用复制算法)
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
Parallel Scavenger 收集器
(新生代采用复制算法)
Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在困难的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
Serial Old 收集器
(老年代,标记-整理算法)
Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
Parallel Old 收集器
(老年代,标记-整理算法)
Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS收集器(重点)
(老年代,标记清除算法)
CMS全称Concurrent Mark Sweep,看名字就知道采用的标记清除算法。它是一种以获取最短回收停顿时间为目标的收集器。它也是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
它的运作过程主要分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对为标记的区域做清扫。
CMS收集器的优点是并发收集、低停顿,它的缺点主要有三点:
1、CMS收集器对CPU资源比较敏感。在并发阶段,会占用一部分CPU资源(线程),从而导致程序变慢。
2、无法处理浮动垃圾。所谓的浮动垃圾指的是并发清除过程中产生的垃圾。因为浮动垃圾的存在,JVM不能等到老年代会满了才进行GC,需要预留一定的空间,默认是68%会触发GC,这个数值是可以通过参数修改的。如果CMS运行期间预留的内存无法满足,就会出现一次"Concurrent Mode Fail"失败,这个时候JVM会启动Serial Old收集器重新进行GC。
3、标记-清除算法带来的空间碎片问题——GC后产生大量的不连续的空间碎片。为了解决这个问题,JVM提供XX:UseCMSCompactAtFullCollection参数,作用是在Full GC后进行一次碎片整理。
G1收集器(重点)
(老年代,标记整理算法)
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与 CMS 的“标记–清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
它有两个显著的优点:
一是标记-整理算法不会产生内存碎片。
二是它可以非常精确地控制停顿,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集的时间不超过N毫秒。
G1收集器可以实现在基本不牺牲吞吐量的情况下完成低停顿的内存回收,这是由于它极力的避免全区域的回收,G1收集器将Java堆(包括新生代和老年代)划分为多个大小固定的独立区域(Region),并且追踪这些区域的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的时间,优先回收垃圾最多的区域 。(这就是Garbage First名称的来由)
这里的G1收集器比较特殊,这里运用 这 里 的两个图,它收集时不区分新生代老年代。忘了这张图。
G1算法将堆划分为若干个区域,它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了。
在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
注意:在java 8中,持久代也移动到了普通的堆内存空间中,改为元空间。
多个收集器的对比
收集器 | 串行、并行or并发 | 新生代/老生代 | 算法 | 目标 | 适应场景 |
---|---|---|---|---|---|
Serial | 串行 | 新生代 | 复制 | 响应速度优先 | 单CPU环境下的Client模式 |
Serial Old | 串行 | 老年代 | 标整 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
ParNew | 并行 | 新生代 | 复制 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 并行 | 新生代 | 复制 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
Parallel Old | 并行 | 老年代 | 标整 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 并发 | 老年代 | 标清 | 响应速度优先 | 集中在互联网站或B/S系统服务端上的Java应用 |
G1 | 并发 | both | 标整+复制 | 响应速度优先 | 面向服务端应用,将来替换CMS |
选择垃圾收集器
-
单CPU或小内存,单机内存
-XX:+UseSerialGC
-
多CPU,需要最大吞吐量,如后台计算型应用
-XX:+UseParallelGC -XX:+UseParallelOldGC
-
多CPU,最求低停顿时间,需快速相应,如互联网应用
-XX:+ParNewGC -XX:+UseConcMarkSweepGC
经典问题
1、JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor?
1)共享内存区划分
- 共享内存区 = 持久代 + 堆
- 持久代 = 方法区 + 其他
- Java堆 = 老年代 + 新生代
- 新生代 = Eden + S0 + S1
2)为什么要分为Eden和Survivor?为什么要设置两个Survivor区?
- 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
- Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
- 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
2.JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代
首先,Java堆 = 老年代 + 新生代,新生代 = Eden + S0 + S1,当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。
大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。
老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。
Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。
(这里MajorGC和MinorGC实战问过效率比问题)。
3.你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。
1)几种垃圾收集器:
- Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。
- ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。
- Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
- Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
- Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。
- CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
- G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。
2)CMS收集器和G1收集器的区别:
- CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
- G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
- CMS收集器以最小的停顿时间为目标的收集器;
- G1收集器可预测垃圾回收的停顿时间
- CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
- G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
4、说说你知道的JVM参数
①堆栈相关
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0
-Xmx3550m: 最大堆大小为3550m。
-Xms3550m: 设置初始堆大小为3550m。
-Xmn2g: 设置年轻代大小为2g。
-Xss128k: 每个线程的堆栈大小为128k。
-XX:MaxPermSize: 设置持久代大小为16m
-XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。
-XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。
②垃圾收集器相关
-XX:+UseParallelGC
-XX:ParallelGCThreads=20
-XX:+UseConcMarkSweepGC
-XX:CMSFullGCsBeforeCompaction=5
-XX:+UseCMSCompactAtFullCollection:
-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。
-XX:ParallelGCThreads=20: 配置并行收集器的线程数
-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片
③辅助信息相关
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGC 输出形式:
[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails 输出形式:
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs
5、STW是什么?为什么需要?
Stop一the一World,简称STW,指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。
垃圾回收是根据可达性分析算法,搜索GC Root根的引用链,将不在引用链上的对象当做垃圾回收,设想我们执行某个方法的时候,此时产生了很多局部变量,刚好老年代满了需要进行Full gc,如果不停止线程,垃圾回收正在根据这些局部变量也就是GC Root根搜索引用链,此时这个方法结束了,那么这些局部变量就都会被销毁,这些引用链的GC Root根都销毁了,这些引用当然也成了垃圾对象,这样就会导致在垃圾回收的过程中还会不断的产生新的垃圾。
但是Stop-The-World的结果是比较严重的,如果用户正在浏览你的网站,应用程序突然Stop-The-World,所有线程被挂起,那么用户就会感觉你的网站卡住了,尽管gc时间是比较快的,但是如果并发量比较大,用户感知是比较明显的,会影响用户体验。
JVM调优案例https://blog.csdn.net/weixin_44704538/article/details/108222022
部分图来自https://blog.csdn.net/weixin_33656548/article/details/102972663
部分面试题来自https://blog.csdn.net/qq_41701956/article/details/100074023