2024年Go最新【JVM】Java垃圾回收与垃圾收集器_java 主动发起回收(2),京东最新Golang面试真题解析

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

1、判断对象是否存活
(1)引用计数算法(Reference Counting)

引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。给对象中添加一个引用计数器,每当对象被引用时,计数器就加 1;当引用失效,计数器就减 1;若某一个对象引用计数器的值为0,那么表示这个对象没有被其他对象引用,是一个失效的垃圾对象,被当做垃圾回收

  • 优点:实现简单、判定效率高,程序执行受影响较小。(Python使用,微软的COM技术使用)
  • 缺点:无法识别对象之间的相互循环引用,容易导致内存泄露。(java语言没有使用)在这里插入图片描述

下面这段代码是用来验证引用计数算法能不能检测出循环引用。testGC ()方法:对象objA和objB都有字段instance,赋值令objB.instance = objA及objA.instance = objB,程序最后将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,如果JVM通过引用计数法来判断对象是否存活,就无法通知GC收集器回收它们,这样容易造成内存泄露。

/\*\*
\*teatGC()方法执行后,objA和objB会不会被GC呢?
\*@auther zzm
\*/
public class ReferenceCountingGC {

    public Object instance = null;
    private static final int _1MB = 1024 \* 1024;
    
    /\*\*
 \*这个成员属性是为了占点内存,以便能在GC日志中看清楚是否被回收过
 \*/
    private byte[] bigSize = new byte[2 \* _1MB];
    
        public static void testGC() {
            ReferenceCountingGC objA = new ReferenceCountingGC();
            ReferenceCountingGC objB = new ReferenceCountingGC();
            // 对象之间相互循环引用,对象objA和objB之间的引用计数永远不可能为 0
            objB.instance = objA;
            objA.instance = objB;
            
            objA = null;
            objB = null;
            
            //假设在这行发生GC,objA和objB是否能被回收?
            System.gc();
        }
    }

运行结果:
[GC (System.gc()) [PSYoungGen: 7168K->792K(35840K)] 7168K->800K(117760K), 0.0024442 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 792K->0K(35840K)] [ParOldGen: 8K->667K(81920K)] 800K->667K(117760K), [Metaspace: 3491K->3491K(1056768K)], 0.0101934 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 35840K, used 307K [0x00000000d8700000, 0x00000000daf00000, 0x0000000100000000)
  eden space 30720K, 1% used [0x00000000d8700000,0x00000000d874ce40,0x00000000da500000)
  from space 5120K, 0% used [0x00000000da500000,0x00000000da500000,0x00000000daa00000)
  to   space 5120K, 0% used [0x00000000daa00000,0x00000000daa00000,0x00000000daf00000)
 ParOldGen       total 81920K, used 667K [0x0000000089400000, 0x000000008e400000, 0x00000000d8700000)
  object space 81920K, 0% used [0x0000000089400000,0x00000000894a6ff8,0x000000008e400000)
 Metaspace       used 3498K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

从运行结果可以看到,GC日志中包含“800K->667K”,意味着JVM并没有因为这两个对象互相引用就不执行垃圾回收,这也说明JVM并不是通过引用计数算法来判断对象是否存活的。

(2)可达性分析算法(根搜索算法)

可达性分析算法是通过判断对象的引用链是否可达来决定对象是否可以被回收。通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(意味着GC Roots到这个对象不可达),证明该对象是不可用的,被当做垃圾回收

  • 优点:可以解决循环引用的问题,不需要占用额外的空间;
  • 缺点:多线程场景下,其他线程可能会更新已经访问过的对象的引用;

在这里插入图片描述

(3)JVM中4种引用和使用场景

强引用、软引用、弱引用、虚引用,引用强度越来越低,引用强度越弱的对象越容易被垃圾回收,Java垃圾回收器会优先清理可达性强度低(引用强度弱)的对象。
  JVM中4种引用和使用场景

(4)判断对象的可达性

若一个对象的引用类型有多个,如何判断他的可达性?(单弱多强)

  • 单条引用链的可达性以最弱的一个引用类型来决定;
  • 多条引用链的可达性以最强的一个引用类型来决定;
(5)GC Roots对象

Java语言中可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性(静态成员)或常量引用的对象;
  • 本地方法栈中JNI(Native方法)引用的对象。
(6)Stop-The-World(STW)

Stop-The-World:由虚拟机在后台自动发起、自动完成,在用户不可见的情况下把用户正常工作的线程全部停掉,即GC停顿。STW是导致GC卡顿的重要原因之一,所以尽可能减少STW的时间,就是我们优化JVM的主要目标。

可达性分析时为了确保对象之间的引用关系不会被打乱(快照的一致性),需要对整个系统进行冻结,暂停所有工作线程,工作线程不停止的话,会有新对象生成,打乱对象之间的引用关系。不能出现分析过程中对象引用关系还不断变化的情况,如果对象之间的引用关系一直在变化的话是无法真正去遍历它的。

(7)不可达的对象是否是必死之局呢?

在可达性分析算法中不可达的对象,也并非非死不可的,对象可以在被GC时自我拯救,这种自救的机会只有一次
  对象的自我拯救

2、方法区如何判断是否需要回收?

方法区的内存回收目标主要是针对 常量池的回收对类型的卸载。JVM规范不要求在方法区实现垃圾收集,在方法区进行垃圾收集性价比较低,在堆的新生代中,一次可回收70%~90%的空间,永久代的垃圾收集效率元低于此。 是否是废弃常量可以通过该常量是否被其他地方引用来判断,判断是否是无用的类则需要同时满足下面3个条件:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
  • 加载该类的ClassLoader已经被回收;
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述3个条件的无用类进行回收(卸载),这里说的仅仅是“可以”,而不是和对象一样,不使用了就必然会回收。特别地,在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

三、When:何时触发垃圾回收机制GC?(Minor GC和Full GC)

1、什么时候进行垃圾回收?
  • 会在cpu空闲的时候自动进行回收 ;
  • 在堆内存存储满了(新生代或老年代)之后;
  • 主动调用System.gc()方法后尝试进行回收;

GC本身是会周期性的自动运行的,由JVM决定运行的时机,而且现在的版本有多种更智能的模式可以选择,还会根据运行的机器自动去做选择,就算真的有性能上的需求,也应该去对GC的运行机制进行微调,而不是通过使用System.gc()命令来实现性能的优化。

2、Minor GC (新生代GC)
  • Minor GC:新生代GC,是指从新生代空间(包括 Eden 和 Survivor 区域)回收内存。新生代中大多对象朝生夕灭,所以Minor GC发生的非常频繁,回收速度也快。Minor GC速度一般比Full GC快10倍以上。
  • 回收过程:复制算法。因为新生代大多对象朝生夕灭,每次会有大批对象死去,只有少量存活,所以选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
  • 触发条件:
    (1)当Eden区满时;
    (2)新创建的对象的大小大于Eden区剩余的空间时。
3、Full GC (老年代GC)
  • Full GC:又叫Major GC,发生在老年代的垃圾回收动作。Major GC 经常会伴随至少一次 Minor GC。由于老年代中的对象生命周期比较长,因此 Major GC 并不频繁,一般都是等待老年代满了后才进行 Full GC,而且其速度一般会比 Minor GC 慢10倍以上。在进行Full GC前一般都先进行了一次MinorGC,使得有新生代的对象晋升入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
  • 回收过程:标记—清除算法,标记—整理算法。老年代对象存活率高,没有额外空间进行分配担保,所以使用标记—清除算法,标记—整理算法。
  • 触发条件:
    (1)调用System.gc()时,系统建议执行Full GC,但是不必然执行;
    (2)老年代空间不足、永久代空间不足;
    (3)通过Minor GC后进入老年代对象的平均大小大于老年代的可用内存
    (4)在新生代回收内存时,由Eden区和Survivor From区把存活的对象向Survivor To区复制时,对象大小大于Survivor To空间的可用内存,则把该对象转存到老年代(这个过程称为分配担保),且老年代的可用内存小于该对象大小。即老年代无法存放下新生代过度到老年代的对象的时候,便会触发Full GC。
4、我们可以主动进行垃圾回收吗?

我们可以主动调用System.gc()方法进行垃圾回收,每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行。java.lang.System.gc()只是java.lang.Runtime.getRuntime().gc()的简写,两者的行为没有任何不同。

补充:System.gc()用于调用垃圾收集器,在调用时,垃圾收集器将运行以回收未使用的内存空间,它将尝试释放被丢弃对象占用的内存。 然而System.gc()调用附带一个免责声明,它只是建议JVM安排GC运行, 还有可能完全被拒绝,无法保证对垃圾收集器的调用,所以System.gc()并不能说是完美的主动进了垃圾回收。

四、How:对象如何被回收?(垃圾回收算法)

在可达性分析法中,对象有两种状态,那么是可达的、要么是不可达的,在判断一个对象的可达性的时候,就需要对对象进行标记。关于标记阶段,有几个关键点是值得我们注意的,分别是:

  • 开始进行标记前,需要先暂停应用线程,否则如果对象图一直在变化的话是无法真正去遍历它的。暂停应用线程以便 JVM 可以尽情地进行垃圾回收的这种情况又被称之为安全点(Safe Point),这会触发一次 Stop The World(STW)暂停。触发安全点的原因有许多,但最常见的应该就是垃圾回收了。所以尽可能减少STW的时间,就是我们优化JVM的主要目标。

安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的。“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生安全点。对于安全点,另一个需要考虑的问题就是如何在 GC 发生时让所有线程(这里不包括执行 JNI 调用的线程)都“跑”到最近的安全点上再停顿下来。两种解决方案:

  • 抢先式中断(Preemptive Suspension):抢先式中断不需要线程的执行代码主动去配合,在 GC 发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。现在几乎没有虚拟机采用这种方式来暂停线程从而响应 GC 事件。
  • 主动式中断(Voluntary Suspension):主动式中断的思想是当 GC 需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志地地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。
  • 暂停时间的长短并不取决于堆内对象的多少也不是堆的大小,而是存活对象的多少。因此,调高堆的大小并不会影响到标记阶段的时间长短。
1、标记—清除算法(Maik—Sweep)

(1)基本过程:标记-清除算法是一种最基础的算法,后面的收集算法都是根据这种算法的不足进行改进而得到的。分为标记和清除两个阶段。

  • 标记:标记的过程就是遍历所有GC Roots,然后标记出所有需要回收的对象(GC Roots不可达的对象)
  • 清除:遍历堆中所有对象,将标记的对象全部回收掉

(2)优缺点:

  • 优点:不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效
  • 效率问题:标记和清除效率都不高(标记和清除都需要遍历全堆对象),这种方法需要使用一个空闲列表来记录所有的空闲区域以及大小,对空闲列表的管理会增加分配对象时的工作量;
  • 空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够的连续内存从而不得不再次触发垃圾收集动作。

下图为“标记-清除”算法的示意图:
在这里插入图片描述
下图为使用“标记-清除”算法回收前后的状态:
在这里插入图片描述

2、标记—整理算法(Mark—Compact)

(1)基本过程:标记-整理算法标记的过程与标记-清除算法中的标记过程一样,但对标记出的垃圾对象的处理情况有所不同。在基于“标记-整理”算法的收集器的实现中,一般增加句柄和句柄表。

  • 标记:标记的过程就是遍历所有GC Roots,然后标记出所有需要回收的对象(GC Roots不可达的对象)
  • 整理:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

(2)优缺点:

  • 优点:经过整理之后,新对象的分配只需要通过指针碰撞便能完成,比较简单;使用这种方法,空闲区域的位置是始终可知的,也不会再有碎片的问题了。
  • 缺点:GC 暂停的时间会增长,因为你需要将所有的存活对象都拷贝到一个新的地方,还得更新它们的引用地址。每进一次垃圾清除都要频繁地移动存活的对象,效率十分低下。

下图为“标记-整理”算法的示意图:
在这里插入图片描述
下图为使用“标记-整理”算法回收前后的状态:
在这里插入图片描述

3、复制算法(Copying)

(1)基本过程:复制算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它将可用内存按容量划分为大小相同的两块,每次只使用其中的一块(对象面),当这一块的内存用完了,就将还存活着的对象复制到另外一块上面(空闲面),然后再把已使用过的内存空间一次性清理掉。每次都是对整个半区内存回收,没有内存碎片。

(2)优缺点:

  • 优点:标记阶段和复制阶段可以同时进行;每次只对一块内存进行回收,运行高效;只需移动栈顶指针,按顺序分配内存即可,实现简单;内存回收时不用考虑内存碎片的出现。
  • 效率问题:当对象存活率较高时,复制次数过多,效率降低
  • 空间问题:可一次性分配的最大内存缩小了一半,浪费了内存资源,需要额外的空间做分配担保(老年代)(当内存中所有对象100%存活的极端情况)。

下图为复制算法的示意图:
在这里插入图片描述
下图为使用复制算法回收前后的状态:
在这里插入图片描述

事实上,现在商用的虚拟机都采用这种算法来回收新生代。因为研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错。实践中会将新生代内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是 8:1,也就是每次新生代中可用内存空间为整个新生代容量的90% ( 80%+10% ),只有10% 的内存会被“浪费”。

4、分代收集算法(Generational Collection)

分代收集算法是基于这样一个事实:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的算法进行回收可以提高 JVM 的执行效率

堆结构分代的意义:堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中。所以给堆内存分代就是为了提高垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率。

  • Java虚拟机根据对象对象存活的周期不同,一般将堆内存划分为新生代(Young Generation),老年代(Tenured Generation),永久代(Permanet Generation)(对HotStop虚拟机而言)。

值得注意的是,在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。

方法区是接口,永久代是接口实现类,永久代实现了方法区。

在这里插入图片描述
在这里插入图片描述

(1)新生代(Young Generation)

一般情况下,所有新生成的对象首先都是放在新生代的新生代中大多对象朝生夕灭。对象存活率低,所以Minor GC发生的非常频繁,回收速度也快。新生代内存按照 8:1:1 的比例分为一个eden区和两个survivor(survivor0,survivor1)区,大部分对象在Eden区中生成。当新对象生成,Eden 空间申请失败(因为空间不足等),则会发起一次 GC(Scavenge GC)。在进行垃圾回收时,先将eden区存活对象复制到survivor0区,然后清空eden区,当这个survivor0区也满了时,则将eden区和survivor0区存活对象复制到survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后交换survivor0区和survivor1区的角色(即下次垃圾回收时会扫描Eden区和survivor1区),即保持survivor0区为空,如此往复。交替使用、循环往复。

特别地,当survivor1区也不足以存放eden区和survivor0区的存活对象时,就将存活对象直接存放到老年代。如果老年代也满了,就会触发一次FullGC,也就是新生代、老年代都进行回收。

当对象在 Survivor 区躲过一次 GC 的话,其对象年龄便会加 1,默认情况下,如果对象年龄达到 15 岁,(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),就会移动到老年代中。若是老年代也满了就会触发一次 Full GC。新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制 Eden 和 Survivor 的比例。

(2)老年代(Tenured Generation)

老年代一般存放的是一些生命周期较长的对象,在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。此外,老年代的内存也比新生代大很多(大概比例是1:2),当老年代满时会触发Major GC(Full GC),老年代对象存活时间比较长,存活率高,因此FullGC发生的频率比较低
  一般来说,大对象会被直接分配到老年代。所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组。当然分配的规则并不是百分之百固定的,这要取决于当前使用的是哪种垃圾收集器组合和 JVM 的相关参数。

(3)永久代(Permanet Generation)

用于存放静态文件(class类、方法)和常量等。永久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如 Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。对永久代(方法区)的回收主要回收两部分内容:废弃常量和无用的类。永久代在 Java SE8 特性中已经被移除了,取而代之的是元空间(MetaSpace),因此也不会再出现java.lang.OutOfMemoryError: PermGen error的错误了。

(4)分代收集

分代收集算法就是根据各个年代的特点选择最适合的收集算法。

  • 新生代中,对象的大量死亡,只有少数对象存活,复制算法最合适,只需要付出少量存活对象的复制成本就可以完成垃圾收集。
  • 老年代中,对象存活率高,没有额外的空间对它进行分配担保,所以标记—清理算法或者标记—整理算法值是最合适的。

五、垃圾收集器(内存回收的具体实现)

1、垃圾收集器

JVM是一个进程垃圾收集器就是一个线程,垃圾收集线程是一个守护线程,优先级低,其在当前系统空闲或堆中老年代占用率较大时触发。GC线程与应用线程保持相对独立,当系统需要执行垃圾回收任务时,先停止工作线程,然后命令 GC 线程工作。

下图展示了7种作用于不同分代的收集器,虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器;如果两个收集器之间存在连线,就说明它们可以搭配使用:Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1。

在这里插入图片描述

(一)垃圾收集器分类

(1)新生代收集器还是老年代收集器:

  • 新生代收集器:Serial、ParNew、Parallel Scavenge;
  • 老年代收集器:Serial Old、Parallel Old、CMS;
  • 整堆收集器:G1

(2)吞吐量优先、停顿时间优先

  • 吞吐量优先:Parallel Scavenge收集器、Parallel Old 收集器
  • 停顿时间优先:CMS(Concurrent Mark-Sweep)收集器

(3)吞吐量与停顿时间适用场景

  • 停顿时间优先:交互多,对响应速度要求高。
  • 吞吐量优先:交互少,计算多,适合在后台运算的场景。

吞吐量(Throughput)就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

(4)算法

  • 复制算法:Serial收集器、ParNew收集器、Parallel Scavenge收集器、G1收集器
  • 标记-清除:CMS收集器
  • 标记-整理:Serial Old收集器、Parallel Old收集器、G1收集器

(5)串行并行并发

  • 串行:Serial收集器、Serial Old收集器
  • 并行:ParNew收集器、Parallel Scavenge收集器、Parallel Old收集器
  • 并发:CMS收集器、G1收集器

以串行模式工作的收集器,称为Serial Collector,即串行收集器;与之相对的是以并行模式工作的收集器,称为Paraller Collector,即并行收集器。

  • 串行(Serial):使用单线程进行垃圾回收的收集器。通过名字就可以看出来,串行的都带有Serial关键字。
  • 并行(Parallel):多条垃圾收集线程并行工作,用户线程处于等待状态。通过名字就可以看出来,并行的都带有Parallel关键字,ParNew的Par也是Parallel缩写。
    在这里插入图片描述
  • 并发(Concurrnet):用户线程与垃圾收集线程同时执行(不一定是并行,可以是交替执行),用户线程在继续执行,而垃圾收集器运行在另一个CPU上。
    在这里插入图片描述
(二)垃圾收集器
(1)Serial收集器(复制算法)
  • Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器,JDK1.3.1前是HotSpot新生代收集的唯一选择。新生代单线程串行收集器,标记和清理都是单线程,简单高效。单线程一方面意味着它只会使用一个CPU或一条线程去完成垃圾收集工作,另一方面也意味着在它进行垃圾收集时,必须暂停其他所有的工作线程,即 Stop-the-world,直到它收集结束为止。

Stop the World是在用户不可见的情况下执行的,会造成某些应用响应变慢;Stop-the-world 暂停时间的长短,是衡量一款收集器性能高低的重要指标。

  • 应用场景:Serial收集器没有线程交互的开销,单线程收集效率高,Serial收集器是虚拟机运行在Client模式下的默认新生代收集器
  • 参数:
    –XX:+UseSerialGC:串联收集器,在JDK Client模式,不指定VM参数,默认是串行垃圾回收器。
(2)ParNew收集器(复制算法)
  • 新生代多线程并行收集器,ParNew收集器就是Serial收集器的多线程版本,采用多个 GC 线程并行收集。它也是一个新生代收集器,除了使用多线程进行垃圾收集外,其余行为包括Serial收集器可用的所有控制参数、收集算法(复制算法)、Stop The World、对象分配规则、回收策略等与Serial收集器完全相同,两者共用了相当多的代码,在多核CPU环境下有着比Serial更好的表现。

一般来说,与串行收集器相比,在多处理器环境下工作的并行收集器能够极大地缩短 Stop-the-world 时间。

  • 应用场景:ParNew收集器是运行在Server模式下的虚拟机首选的新生代收集器。很重要的原因是:除了Serial收集器之外,目前只有它能与CMS收集器配合工作。
  • 参数:
    -XX:+UseConcMarkSweepGC:指定使用CMS后,会默认使用ParNew作为新生代收集器;
    -XX:+UseParNewGC:强制指定使用ParNew;
    -XX:ParallelGCThreads:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
(3)Parallel Scavenge收集器(复制算法)
  • 新生代并行多线程收集器,追求高吞吐量,高吞吐量可以高效率的利用CPU,尽快完成程序的运算任务,适合后台应用等对用户交互相应要求不高的场合。

Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为吞吐量优先收集器。Parallel Scavenge收集器与ParNew收集器的一个重要区别是它具有自适应调节策略

  • 应用场景:Parallel Scavenge收集器是虚拟机运行在Server模式下的默认垃圾收集器。高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;适合那种交互少、运算多的场景,例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。
  • 参数:
    -XX:+MaxGCPauseMillis:控制最大垃圾收集停顿时间,大于0的毫秒数;这个参数设置的越小,停顿时间可能会缩短,但也会导致吞吐量下降,导致垃圾收集发生得更频繁。
    -XX:GCTimeRatio:设置垃圾收集时间占总时间的比率,0<n<100的整数,就相当于设置吞吐量的大小。
    –XX:+UseAdptiveSizePolicyGC自适应的调节策略(GC Ergonomiscs)开关参数,开启这个参数后,就不用手工指定一些细节参数,如:新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等;JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量。

先垃圾收集执行时间占应用程序执行时间的比例的计算方法是:1 / (1 + n);例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5%=1/(1+19);默认值是1%=1/(1+99),即n=99;垃圾收集所花费的时间是年轻一代和老年代收集的总时间;

另外值得注意的一点是,Parallel Scavenge收集器无法与CMS收集器配合使用,所以在JDK 1.6推出Parallel Old之前,如果新生代选择Parallel Scavenge收集器,老年代只有Serial Old收集器能与之配合使用。

(4)Serial Old收集器(标记- 整理算法)

在这里插入图片描述

  • 老年代单线程串行收集器,Serial收集器的老年代版本,除了收集算法不同,两个版本并没有其他差异。
  • 应用场景:
    Client模式:Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xwMTU5Mjk4MDE5MDc=,size_16,color_FFFFFF,t_70#pic_center)

  • 老年代单线程串行收集器,Serial收集器的老年代版本,除了收集算法不同,两个版本并没有其他差异。
  • 应用场景:
    Client模式:Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用

[外链图片转存中…(img-Eal8t8vr-1715641574597)]
[外链图片转存中…(img-aOMKw1xr-1715641574598)]
[外链图片转存中…(img-kVIY8xZZ-1715641574598)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 17
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值