JVM 内存模型详解

引子:

想要学习 JVM 内存模型,首先我们需要知道它到底是个什么东西,以下是官网对它的介绍:
JVM 是 Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在
实际的计算机上仿真模拟各种计算机功能来实现的。主流虚拟机

正文:

首先介绍一下 JVM 和操作系统的关系:

Java是一门抽象程度特别高的语言,提供了自动内存管理等一系列的特性。这些特性直接在操作系统上实现是不太可能的,所以就需要JVM进行一番转换。

从图中可以看到,有了JVM这个抽象层之后,Java就可以实现跨平台了。JVM只需要保证能够正确执行.class文件,就可以运行在诸如Linux、Windows、MacOS等平台上了。而Java跨平台的意义在于一次编译,处处运行,能够做到这一点JVM功不可没。
一个java文件的整个执行流程:
image.png

jdk、jre、jvm三者的关系:

image.png
jdk是java开发工具包,jre是java开发环境。java开发工具包就包含了java开发环境,而java开发环境其中就包含了jvm和很多的java类库。如果你想运行一个java程序,那么只要你在计算机上安装一个jre就可以运行,但是如果你想要开发java项目那就需要安装jdk工具包去实现更多的功能。
严谨的来说:
JVM是Java程序能够运行的核心。但需要注意,JVM自己什么也干不了,你需要给它提供生产原料(.class文件)。仅仅是JVM,是无法完成一次编译,处处运行的。它需要一个基本的类库,比如怎么操作文件、怎么连接网络等。而Java体系很慷慨,会一次性将JVM运行所需的类库都传递给它。JVM标准加上实现的一大堆基础类库,就组成了Java的运行时环境,也就是我们常说的JRE(JavaRuntimeEnvironment)对于JDK来说,就更庞大了一些。除了JRE,JDK还提供了一些非常好用的小工具,比如javac、java、jar等。它是Java开发的核心。我们也可以看下JDK的全拼,JavaDevelopmentKit。JVM、JRE、JDK它们三者之间的关系,可以用一个包含关系表示。
image.png

jvm内存结构图解

jvm内存模型又分为线程共享区域和线程私有区域
推、方法区属于线程共享区域,虚拟机栈、本地方法栈、程序技术器属于线程私有区域。

栈:

主要用于储存局部变量表、操作数栈、动态链接、方法出口等信息,它的生命周期与线程相同。
虚拟机栈描述的是java方法执行的内存模型:
每个方法在执行的时候都会创建一个栈帧(Stack Fram),拿main方法举例,当main方法开始运行时会创建一个线程,创建线程的时候,就会在栈内存中分配一小块内存用来存该线程的局部变量等信息,那这一小块内存就叫栈帧。每一个方法从调用到执行完成,也就是栈帧在虚拟机栈中的入栈和出栈的过程。虚拟机栈可以比作是一个没有盖子的桶,先入栈的栈帧反而最后才会出栈,遵循先入后出的原则。

  • 局部变量表**:**顾名思义,用来存放局部变量的。详细说明就是存放了编译期的各种基本数据类型(boolean、long、char、short、int、float、long、double)、对象引用(reference类型,可能是一个指向对象起始地址的引用指针)和returnAddress类型(指向字节码指令的地址)。
  • 操作数栈**:**一块临时的内存空间,用来存储变量所对应的值。比如 int a = 1; int b = 2; int c = a+b; 那么这里的a ,b,c在对它们赋值之前,这些值是存放在操作数栈中的,这个过程称为压栈,当进行赋值操作时称为出栈。 c的值是先将a和b的值,在操作数栈,也就是赋值之前,先让cpu进行计算后,才进行对c的赋值。
  • 动态链接**:**简单来说,当在main方法里面,你写一个调用外部方法的代码,那么当这个类初始化加载的时候,并不会把这个外部方法加载进去,而是main线程执行到那一步的时候jvm才会去加载解析。
  • 方法出口**:**当我们在main方法中调用外部方法时,那么当外部方法执行完以后还是要回到调用时的地方继续向下执行,那么怎么在外部方法执行完以后还能回到调用的地方,就是方法出口所在的事情,它会进行记录,方法出口就是记录一些方法的信息的。

程序计数器:

主要用来告诉线程字节码执行到了哪一个位置,用来计算执行行数的。下面有篇文章详细讲解了,程序计数器的具体操作https://www.cnblogs.com/ITPower/p/15381001.html

方法区:

主要存放运行时常量池,常量 + 静态变量 + 类元信息(就是类的代码信息)
举例:User user = new User(); 这里的user就会被保存到方法区中,而new User();被保存到推内存中,当我们创建这个user是会生成一个起始的内存地址,这个内存地址会被保存在栈中局部变量表。

堆内存:

大部分的对象实例以及数组都要在堆上分配。
主要存放创建对象和数组,是jvm里最大的一块内存,也是垃圾收集器管理的主要区域,所以也叫GC堆,jvm调优就是在堆内存上进行调优,由于现在收集器基本都采用分代收集算法,所以堆内存又细分为了新生代和老年代;

本地方法栈;

本地栈涉及到native关键字,和虚拟机栈一样也是用来调用方法的,区别是本地方法栈是用来调用计算机本地由c++语言实现的方法,如果有些功能用java没办法完成,就会使用 native关键词来调用本地方法进行实现。


都有哪些类加载器

根类加载器(Bootstrap class loader):是有c++实现的,负责加载支撑JVM运行的, 位于jre目录的lib目录下的核心类. 比如:rt.jar, charset.jar等。
扩展类加载器(Extensions class loader):负责加载支撑JVM运行的, 位于jre目录的lib/ext扩展目录中的jar包。
应用类加载器(Application class loader):负责加载classPath路径下的类包, 主要加载自己写的类。

双亲委派机制

双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式.
优点:避免了类的重复加载,避免了核心类库被修改;

分代收集算法

主要特性:

  1. 对象优先在 Eden 区分配。
  2. 大对象直接进入老年代。
  3. 长期存活的对象将进入老年代,默认为 15 岁。

详细介绍:

上面有说到,jvm中堆内存占用的空间最大,垃圾回收也主要是针对堆内存进行,而堆内存又细分新生代和老年代。新生代又细分为Eden区、survivor0区、survivor1区,内存比例为8:1:1。
新生代:
Eden区主要功能是给创建的新生对象和数组分配内存空间,当Eden区内存不足时,就会触发轻GC,轻GC采用复制算法进行垃圾回收,先使用可达性分析法将Eden区的垃圾对象进行标记,然后将存活对象复制到survivor0区,最后清空Eden区。当survivor0区内存也不足时,则将Eden区和survivor0区都复制到survivor1区,然后清空Eden区survivor0区,此时 Survivor0 区是空的,然后将 Survivor0 区和 Survivor1 区交换,即保持 Survivor1 区为空。如此往复,当Survivor1区内存也满时,就会将经历多次垃圾回收还侥幸存活的对象放进老年代中,当对象在 Survivor 区躲过一次 GC 的话,其对象年龄便会加 1,正常年龄为15时就会被放进老年代。若是老年代也满了就会触发一次 Full GC(重GC),也就是新生代、老年代都进行回收。新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制 Eden 和 Survivor 的比例。
老年代:
首先是进入老年代的条件:一般来说,大对象会被直接分配到老年代。所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组。还有一种是在新生代经过多次垃圾回收对象年龄达到15的对象,也会被放进老年代,因此,可以认为老年代存放的都是生命周期比较长的对象。当老年代内存也已经满的时候,就会触发重GC,对于晋升老年代的分代年龄阈值,我们可以通过-XX:MaxTenuringThreshold参数进行控制。
在这里,不知道大家有没有对这个默认的 15 岁分代年龄产生过疑惑,为什么不是 16 或者 17 呢?可以简单了解一下。
实际上,HotSpot 虚拟机的对象头其中一部分用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,这部分数据的长度在 32 位和 64 位的虚拟机(未开启压缩指针)中分别为 32bit 和 64bit,官方称它为Mark word。
例如,在 32 位的 HotSpot 虚拟机中,如果对象处于未被锁定的状态下,那么Mark Word的 32bit 空间中 25bit 用于存储对象哈希码,4bit 用于存储对象分代年龄,2bit 用于存储锁标志位,1bit 固定为 0,其中对象的分代年龄占 4 位,也就是从0000到1111,而其值最大为 15,所以分代年龄也就不可能超过 15 这个数值了。

都有哪些垃圾回收算法

标记清除:

首先虚拟机会通过可达性算法标记出所有未被任何对象所引用的对象,也就是可回收对象,然后将这些已标记的对象进行垃圾回收。
优点:不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效。
缺点:会产生很多内存碎片,也就是不连续的内存空间,就比如还剩10mb内存可用,但是因为内存的不连续,每一小块空间只有2mb,那么当创建一个5mb对象的时候,明明剩余内存是充足的,但是却放不下了。
image.png

标记整理:

标记整理算法标记的过程与“标记-清除”算法中的标记过程一样,可以当做是标记清除的一个优化,标记整理算法在回收完垃圾对象以后会对碎片化的内存进行整理,使剩余的内存仍然保持连续可用的。
优点:经过整理之后,新对象的分配只需要通过指针碰撞便能完成,比较简单;使用这种方法,空闲区域的位置是始终可知的,也不会再有碎片的问题了。
缺点:GC 暂停的时间会增长,因为你需要将所有的对象都拷贝到一个新的地方,还得更新它们的引用地址。
image.png

复制算法:

复制算法相当于把内存一分为二,分为对象区和空闲区,空闲区用来复制存活对象,对象区用来进行垃圾回收。先标记可回收对象同时复制不可回收对象到另一半,最后清空掉剩下的一半所有对象。
优点:标记阶段和复制阶段可以同时进行;每次只对一块内存进行回收,运行高效;只需移动栈顶指针,按顺序分配内存即可,实现简单;内存回收时不用考虑内存碎片的出现。
缺点:需要一块能容纳下所有存活对象的额外的内存空间。因此,可一次性分配的最大内存缩小了一半。
image.png

可达性分析法

简介:

可达性分析法也称为根搜索法,主要就是通过对象之间是否可达进行判断是否该回收,可达性是指,如果一个对象会被至少一个在程序中的变量通过直接或间接的方式被其他可达的对象引用,则称该对象就是可达的。更准确的说,一个对象只有满足下述两个条件之一,就会被判断为可达的:

  • 对象是属于根集中的对象
  • 对象被一个可达的对象引用
  • GC 判断对象是否可达看的是强引用。

根集中的对象称之为GC Roots,也就是根对象。可达性分析法的基本思路是:将一系列的根对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,如果一个对象到根对象没有任何引用链相连,那么这个对象就不是可达的,也称之为不可达对象。只针对强引用。

  • 强引用: Object obj = new Object(); 这种写法就属于强引用关系。
  • 弱引用:
  • 软引用:
  • 虚引用:

image.png
如上图所示,形象的展示了可达对象与不可达对象的示例,其中灰色的对象虽然它们之间侧也存在引用,但是和根对象是不可达的,所以都是不可达对象,表示可以被垃圾收集的对象。在判断一个对象的可达性的时候,就需要对对象进行标记。
关于标记阶段,在根搜索算法中,要真正宣告一个对象死亡,至少要经历两次标记过程:
如果对象在进行根搜索后发现没有与根对象相连接的引用链,那它会被第一次标记并且进行一次筛选。筛选的条件是此对象是否有必要执行 finalize()方法。当对象没有覆盖finalize()方法,或finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。如果该对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue的队列中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行finalize()方法。finalize()方法是对象逃脱死亡命运的最后一次机会(因为一个对象的finalize()方法最多只会被系统自动调用一次),稍后 GC 将对F-Queue中的对象进行第二次小规模的标记,如果要在finalize()方法中成功拯救自己,只要在finalize()方法中让该对象重新引用链上的任何一个对象建立关联即可。而如果对象这时还没有关联到任何链上的引用,那它就会被回收掉。

什么是GC Roots?

GC Roots就是对象,首先JVM在进行垃圾回收的时候会先停止用户线程 也就是 STW(Stop the world),然后开始枚举根节点,也就是搜索gc roots对象,然后根据gc roots向下搜索,找到所有直接或间接引用的对象和回收不可达对象。那么怎样才能被称为GC Roots对象呢?以下几种可被认定为GC Roots对象:
image.png

暂停用户线程有两种方法:

  • 抢先式中断(Preemptive Suspension):抢先式中断不需要线程的执行代码主动去配合,在 GC 发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。现在几乎没有虚拟机采用这种方式来暂停线程从而响应 GC 事件。
  • 主动式中断(Voluntary Suspension):主动式中断的思想是当 GC 需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志地地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。

安全点是什么?

安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的。例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生安全点。

都有哪些垃圾回收器

垃圾回收(GC)线程和应用线程是相互独立的关系,当虚拟机进行垃圾回收时用户线程暂停,GC线程开始工作,以串行模式工作的收集器,称为Serial Collector,即串行收集器;与之相对的是以并行模式工作的收集器,称为Paraller Collector,即并行收集器。

G1 收集器(重点了解)

G1(Garbage First)重新定义了堆空间,打破了原有的分代模型,将堆划分为一个个区域(Regina)。这么做的目的是在进行收集时不必在全堆范围内进行,这是它最显著的特点。区域划分的好处就是带来了停顿时间可预测的收集模型:用户可以指定收集操作在多长时间内完成,即 G1 提供了接近实时的收集特性。
G1 具备如下特点:

  • 并行与并发:G1 能充分利用多 CPU、多核环境下的硬件优势,使用多个 CPU 来缩短 Stop-the-world 停顿的时间,部分其他收集器原来需要停顿 Java 线程执行的 GC 操作,G1 收集器仍然可以通过并发的方式让 Java 程序继续运行。
  • 分代收集:打破了原有的分代模型,将堆划分为一个个区域。
  • 空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的。但无论如何,这两种算法都意味着 G1 运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。
  • 可预测的停顿:这是 G1 相对于 CMS 的一个优势,降低停顿时间是 G1 和 CMS 共同的关注点。

在 G1 之前的其他收集器进行收集的范围都是整个新生代或者老年代,而 G1 不再是这样。在堆的结构设计时,G1 打破了以往将收集范围固定在新生代或老年代的模式,G1 将堆分成许多相同大小的区域单元,每个单元称为 Region,Region 是一块地址连续的内存空间,G1 模块的组成如下图所示:
image.png
堆内存会被切分成为很多个固定大小的 Region,每个是连续范围的虚拟内存。堆内存中一个 Region 的大小可以通过-XX:G1HeapRegionSize参数指定,其区间最小为 1M、最大为 32M,默认把堆内存按照 2048 份均分。
每个 Region 被标记了 E、S、O 和 H,这些区域在逻辑上被映射为 Eden,Survivor 和老年代。存活的对象从一个区域转移(即复制或移动)到另一个区域,区域被设计为并行收集垃圾,可能会暂停所有应用线程。
如上图所示,区域可以分配到 Eden,Survivor 和老年代。此外,还有第四种类型,被称为巨型区域(Humongous Region)。Humongous 区域是为了那些存储超过 50% 标准 Region 大小的对象而设计的,它用来专门存放巨型对象。如果一个 H 区装不下一个巨型对象,那么 G1 会寻找连续的 H 分区来存储。为了能找到连续的 H 区,有时候不得不启动 Full GC。
G1 收集的运作过程大致如下:

  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改 TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的 Region 中创建新对象,这阶段需要停顿线程,但耗时很短。
  • 并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。
  • 最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中,这阶段需要停顿线程,但是可并行执行。
  • 筛选回收(Live Data Counting and Evacuation):首先对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。这个阶段也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。

G1 的 GC 模式可以分为两种,分别为:

  • Young GC:在分配一般对象(非巨型对象)时,当所有 Eden 区域使用达到最大阀值并且无法申请足够内存时,会触发一次 YoungGC。每次 Young GC 会回收所有 Eden 以及 Survivor 区,并且将存活对象复制到 Old 区以及另一部分的 Survivor 区。
  • Mixed GC:当越来越多的对象晋升到老年代时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即 Mixed GC,该算法并不是一个 Old GC,除了回收整个新生代,还会回收一部分的老年代,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些 Old 区域进行收集,从而可以对垃圾回收的耗时时间进行控制。G1 没有 Full GC概念,需要 Full GC 时,调用 Serial Old GC 进行全堆扫描。

CMS收集器(重点了解)

CMS(Concurrent Mark Swee)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS 收集器仅作用于老年代的收集,采用“标记-清除”算法,它的运作过程分为 4 个步骤:

  • 初始标记(CMS initial mark)
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)
  • 并发清除(CMS concurrent sweep)

其中,初始标记、重新标记这两个步骤仍然需要 Stop-the-world。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始阶段稍长一些,但远比并发标记的时间短。
CMS 以流水线方式拆分了收集周期,将耗时长的操作单元保持与应用线程并发执行。只将那些必需 STW 才能执行的操作单元单独拎出来,控制这些单元在恰当的时机运行,并能保证仅需短暂的时间就可以完成。这样,在整个收集周期内,只有两次短暂的暂停(初始标记和重新标记),达到了近似并发的目的。
**CMS 收集器优点:**并发收集,低停顿。
CMS 收集器缺点:

  • CMS 收集器对 CPU 资源非常敏感;
  • CMS 收集器无法处理浮动垃圾;
  • CMS 收集器是基于“标记-清除”算法,该算法的缺点都有。

CMS 收集器之所以能够做到并发,根本原因在于采用基于“标记-清除”的算法并对算法过程进行了细粒度的分解。前面已经介绍过“标记-清除”算法将产生大量的内存碎片这对新生代来说是难以接受的,因此新生代的收集器并未提供 CMS 版本。
浮动垃圾
CMS中浮动垃圾的概念,因为在并发标记阶段,GC线程是和用户线程并发进行的,就可能导致有一些垃圾对象没有被标记到也就是被漏掉了,也或者本来该对象被标记为可达的,后面当用户线程运行时又不在引用了变为了不可达对象,这个时候在此次GC中无法将其回收掉,所以这种的就称为浮动垃圾。
在这里可能会有一个疑问,那为什么在重新标记阶段不可以将这种特殊对象给处理掉呢?这是因为重新标记的功能只是将并发标记时所标记的不可达对象进行重新扫描,避免在并发阶段的不可达对象在后面又被用户重新引用。而在并发标记阶段就漏掉的对象在重新标记阶段也是扫描不到的。

Serial 收集器

串行收集器采用单线程方式进行收集,且在 GC 线程工作时,系统不允许应用线程打扰。此时,应用程序进入暂停状态,即 Stop-the-world。Stop-the-world 暂停时间的长短,是衡量一款收集器性能高低的重要指标。Serial 是针对新生代的垃圾回收器,采用“复制”算法。

ParNew 收集器

并行收集器充分利用了多处理器的优势,采用多个 GC 线程并行收集。可想而知,多条 GC 线程执行显然比只使用一条 GC 线程执行的效率更高。一般来说,与串行收集器相比,在多处理器环境下工作的并行收集器能够极大地缩短 Stop-the-world 时间。ParNew 是针对新生代的垃圾回收器,采用“复制”算法,可以看成是 Serial 的多线程版本

Parallel Scavenge 收集器

Parallel Scavenge 是针对新生代的垃圾回收器,采用“复制”算法,和 ParNew 类似,但更注重吞吐率。在 ParNew 的基础上演化而来的 Parallel Scanvenge 收集器被誉为“吞吐量优先”收集器。吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。如虚拟机总运行了 100 分钟,其中垃圾收集花掉 1 分钟,那吞吐量就是99%。
Parallel Scanvenge 收集器在 ParNew 的基础上提供了一组参数,用于配置期望的收集时间或吞吐量,然后以此为目标进行收集。通过 VM 选项可以控制吞吐量的大致范围:

  • -XX:MaxGCPauseMills:期望收集时间上限,用来控制收集对应用程序停顿的影响。
  • -XX:GCTimeRatio:期望的 GC 时间占总时间的比例,用来控制吞吐量。
  • -XX:UseAdaptiveSizePolicy:自动分代大小调节策略。

但要注意停顿时间与吞吐量这两个目标是相悖的,降低停顿时间的同时也会引起吞吐的降低。因此需要将目标控制在一个合理的范围中。

Serial Old 收集器

Serial Old 是 Serial 收集器的老年代版本,单线程收集器,采用“标记-整理”算法。这个收集器的主要意义也是在于给 Client 模式下的虚拟机使用。

Parallel Old 收集器

Parallel Old 是 Parallel Scanvenge 收集器的老年代版本,多线程收集器,采用“标记-整理”算法。

查看 JVM 使用的默认垃圾收集器

在 Mac 终端或者 Windows 的 CMD 执行如下命令:

  • java -XX:+PrintCommandLineFlags -version

参数 含义

  • UseSerialGC 虚拟机运行在 Client 模式下的默认值,打开次开关后,使用Serial + Serial Old的收集器组合进行内存回收
  • UseParNewGC 打开次开关后,使用ParNew + Serial Old的收集器组合进行内存回收
  • UseConcMarkSweepGC 打开次开关后,使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old收集器将作为 CMS 收集器出现Concurrent Mode Failure失败后的备用收集器使用
  • UseParallelGC 虚拟机运行在 Server 模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收
  • UseParallelOldGC 打开此开关后,使用Parallel Scavenge + Parallel Old的收集器组合进行内存回收

由此可知,JDK 8 默认打开了UseParallelGC参数,因此使用了Parallel Scavenge + Serial Old的收集器组合进行内存回收。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值